Compare commits

...
Author SHA1 Message Date
Andre601 1079bc81b5 Small changes to CommandStringBuilder 2025-01-17 14:24:43 +01:00
Andre601 ab68372ceb Merge branch 'master' into feature/standalone-bridge-plugin 2025-01-16 18:00:09 +01:00
Andre601 9d050dee49 Push latest changes. 2025-01-16 02:33:02 +01:00
Andre601 263067376e Commit stuff 2025-01-16 02:32:32 +01:00
Andre601 8b73eb3c8c Change PAPI Placeholder behaviour + new placeholders 2025-01-16 02:27:31 +01:00
Andre601 ce14274e4c Remove PlaceholderAPI page link 2025-01-12 15:34:31 +01:00
Andre601 a5d777f4f1 Restructure Wiki 2025-01-12 15:32:09 +01:00
Andre601 f94e578694 Update formatting.md 2025-01-10 15:28:57 +01:00
Andre601 7c441e6ab5 Update SECURITY.md 2025-01-07 13:36:47 +00:00
Andre601 2779f565a9 Add config option to ignore maintenance for hidePlayersHover 2025-01-06 17:41:37 +01:00
Andre601 6d3cd0c505 Update getting started 2025-01-06 16:33:16 +00:00
Andre601 cf95f11fdb Remove automerge (Useless under current setup) 2025-01-06 15:07:26 +00:00
Andre601 679dbb8c0b Merge pull request 'Update dependency ch.qos.logback:logback-classic to v1.5.16' (#397) from renovate/all-minor-patch into master
Reviewed-on: https://codeberg.org/Andre601/AdvancedServerList/pulls/397
2025-01-06 15:06:29 +00:00
Andre601's Bot f44e2759b9 Update dependency ch.qos.logback:logback-classic to v1.5.16 2025-01-06 12:01:41 +00:00
Andre601 29239cc4ed Add HidePlayersHover to docs 2025-01-05 22:24:16 +01:00
Andre601 bcd5e77166 Try new Bungeecord version to fix CI 2025-01-05 22:02:30 +01:00
Andre601 5d2fa03601 Upload hover examples 2025-01-05 20:39:54 +00:00
Andre601 8f1a5e5a37 Add option to hide online players list. 2025-01-05 20:19:57 +01:00
Andre601 05fde902a4 Merge pull request 'Update dependency space.arim.libertybans:bans-api to v1.1.0-SNAPSHOT' (#395) from renovate/all-minor-patch into master
Reviewed-on: https://codeberg.org/Andre601/AdvancedServerList/pulls/395
2025-01-04 12:18:43 +00:00
Andre601's Bot d69e70c485 Update dependency space.arim.libertybans:bans-api to v1.1.0-SNAPSHOT 2025-01-04 12:16:35 +00:00
Andre601 fba1e84171 Disable dependency Dashboard 2025-01-04 12:15:36 +00:00
Andre601 530f0fd954 Update renovate.json with some automerge and grouping stuff. 2025-01-03 16:06:13 +00:00
Andre601 8bec42bb91 Fix wrong Paper repo URL for renovate 2025-01-03 02:38:41 +00:00
Andre601 94982ff1e8 Merge pull request 'Update dependency com.viaversion:viaversion-api to v5.2.1' (#394) from renovate/com.viaversion-viaversion-api-5.x into master
Reviewed-on: https://codeberg.org/Andre601/AdvancedServerList/pulls/394
2025-01-02 13:29:51 +00:00
Andre601's Bot 1f9795ed97 Update dependency com.viaversion:viaversion-api to v5.2.1 2025-01-02 12:01:33 +00:00
Andre601 c6f95cb78a Update formatting.md 2024-12-30 13:48:48 +01:00
Andre601 571f9f6b38 Add support for SayanVanish 2024-12-29 01:24:55 +01:00
Andre601 6d7fbb8c25 Merge pull request 'Update API reference' (#393) from feature/update-api-reference into master
Reviewed-on: https://codeberg.org/Andre601/AdvancedServerList/pulls/393
2024-12-25 00:58:11 +00:00
Andre601 d3d83e5e11 Update ProfileEntry index page 2024-12-25 00:46:43 +00:00
Andre601 82fb9c90f4 Update API reference 2024-12-25 00:39:03 +00:00
Andre601 6841a5a6e0 Undo componentlogger changes on velocity 2024-12-24 22:27:26 +01:00
Andre601 6f41cd4e3a Use a fallback logger for velocity versions without ComponentLoggers 2024-12-24 22:05:56 +01:00
Andre601 379f36832e Use snippet links 2024-12-24 21:45:01 +01:00
Andre601 5d8a39cd22 Update config example in getting-started 2024-12-24 21:43:39 +01:00
Andre601 9dba820840 Reduce flood of messages on Startup + make ASL banner hidable.
(I really don't get why people get upset about a few lines of text... Won't make the Log file like 10GB bigger or smth...)
2024-12-24 21:41:39 +01:00
Andre601 58712ca5c5 Merge pull request 'Update dependency net.kyori:adventure-text-serializer-legacy to v4.18.0' (#392) from renovate/dependency.adventure-text-serializer-legacy into master
Reviewed-on: https://codeberg.org/Andre601/AdvancedServerList/pulls/392
2024-12-24 12:04:13 +00:00
Andre601's Bot e19e942799 Update dependency net.kyori:adventure-text-serializer-legacy to v4.18.0 2024-12-24 12:01:21 +00:00
Andre601 03434130c4 Update dependency net.kyori:adventure-text-minimessage to v4.18.0 2024-12-23 13:20:48 +01:00
Andre601 99df25551a Merge pull request 'Update dependency net.kyori:adventure-api to v4.18.0' (#390) from renovate/dependency.adventure-api into master
Reviewed-on: https://codeberg.org/Andre601/AdvancedServerList/pulls/390
2024-12-23 12:14:32 +00:00
Andre601's Bot 4f31e4ce6f Update dependency net.kyori:adventure-api to v4.18.0 2024-12-23 12:01:35 +00:00
Andre601 ba097c538d Merge pull request 'Update dependency ch.qos.logback:logback-classic to v1.5.15' (#389) from renovate/ch.qos.logback-logback-classic-1.x into master
Reviewed-on: https://codeberg.org/Andre601/AdvancedServerList/pulls/389
2024-12-22 13:03:31 +00:00
Andre601's Bot aa52435591 Update dependency ch.qos.logback:logback-classic to v1.5.15 2024-12-22 12:01:59 +00:00
Andre601 f44194d24f Update docs 2024-12-21 02:21:33 +01:00
Andre601 ad460f3728 Merge branch 'master' into feature/standalone-bridge-plugin 2024-12-05 23:10:01 +00:00
Andre601 7aba9c6243 Merge branch 'master' into feature/standalone-bridge-plugin 2024-11-19 15:19:02 +00:00
Andre601 ba81bed9a2 Rework logic and make use of already fetching servers 2024-10-21 18:02:01 +02:00
Andre601 f452495d4b Merge branch 'master' into feature/standalone-bridge-plugin 2024-10-20 22:49:57 +00:00
Andre601 e00a628f31 Improve some stuff 2024-09-08 19:57:23 +02:00
Andre601 9d8ccf42fe Add plugin message stuff on BungeeCord side. 2024-09-06 16:40:31 +02:00
Andre601 789e1299ab Create Standalone bridge plugin. 2024-09-05 22:45:09 +02:00
75 changed files with 1623 additions and 620 deletions

No files matched your search

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

+2 -2
View File
@@ -8,12 +8,12 @@ To ensure proper safety and support are only certain versions and Platforms of A
|---------------|-----------|
| v5 | Yes |
| v4 | Yes* |
| v3 | Yes** |
| v3 | No** |
| v2 | No |
| v1 | No |
\*Only receives support but no updates or bug fixes.
\*\*Support ends at the end of 2024.
\*\*Support ended on December 31st, 2024.
## Supported Server/Proxy Versions
+1 -1
View File
@@ -99,7 +99,7 @@
<dependency>
<groupId>space.arim.libertybans</groupId>
<artifactId>bans-api</artifactId>
<version>1.1.0-RC1</version>
<version>1.1.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
+65
View File
@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>parent</artifactId>
<groupId>ch.andre601.advancedserverlist</groupId>
<version>parent</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>asl-placeholderapi</artifactId>
<packaging>jar</packaging>
<version>${plugin.version}</version>
<properties>
<maven.compiler.source>16</maven.compiler.source>
<maven.compuler.target>16</maven.compuler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<repositories>
<repository>
<id>papermc-repo</id>
<url>https://repo.papermc.io/repository/maven-public/</url>
</repository>
<repository>
<id>placeholderapi-repo</id>
<url>https://repo.extendedclip.com/content/repositories/placeholderapi/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>io.papermc.paper</groupId>
<artifactId>paper-api</artifactId>
<version>1.21-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>me.clip</groupId>
<artifactId>placeholderapi</artifactId>
<version>2.11.6</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>AdvancedServerList-PlaceholderAPI-Addon-${plugin.version}</finalName>
<resources>
<resource>
<filtering>true</filtering>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,70 @@
package ch.andre601.placeholderapi;
import ch.andre601.placeholderapi.messenger.ASLPluginMessageListener;
import ch.andre601.placeholderapi.messenger.BungeeCordMessageListener;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.concurrent.Callable;
import java.util.logging.Level;
public final class ASLPlaceholderAPI extends JavaPlugin{
private String serverName = null;
@Override
public void onEnable(){
// Own Plugin Channel
getServer().getMessenger().registerOutgoingPluginChannel(this, pluginChannel());
getServer().getMessenger().registerIncomingPluginChannel(this, pluginChannel(), new ASLPluginMessageListener(this));
// BungeeCord Plugin Channel
getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
getServer().getMessenger().registerIncomingPluginChannel(this, "BungeeCord", new BungeeCordMessageListener(this));
requestServerName();
}
@Override
public void onDisable(){
getServer().getMessenger().unregisterOutgoingPluginChannel(this);
getServer().getMessenger().unregisterIncomingPluginChannel(this);
}
public void serverName(String serverName){
this.serverName = serverName;
}
public String pluginChannel(){
return "advancedserverlist:action";
}
public String serverName(){
return serverName;
}
public void waitUntil(Callable<Boolean> condition, Runnable action){
Bukkit.getScheduler().runTaskTimer(this, (task) -> {
try{
if(condition.call()){
action.run();
task.cancel();
}
}catch(Exception ex){
getLogger().log(Level.WARNING, "Encountered Exception while waiting for a task to finish.", ex);
}
}, 0L, 1L);
}
private void requestServerName(){
waitUntil(() -> !getServer().getOnlinePlayers().isEmpty(), () -> {
Player plmSender = Bukkit.getOnlinePlayers().iterator().next();
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("GetServer");
plmSender.sendPluginMessage(this, "BungeeCord", out.toByteArray());
});
}
}
@@ -0,0 +1,78 @@
package ch.andre601.placeholderapi.messenger;
import ch.andre601.placeholderapi.ASLPlaceholderAPI;
import me.clip.placeholderapi.PlaceholderAPI;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.bukkit.plugin.messaging.PluginMessageListener;
import org.jetbrains.annotations.NotNull;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.util.UUID;
import java.util.logging.Level;
public class ASLPluginMessageListener implements PluginMessageListener{
private final ASLPlaceholderAPI plugin;
public ASLPluginMessageListener(ASLPlaceholderAPI plugin){
this.plugin = plugin;
}
@Override
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, byte[] message){
if(!channel.equals(plugin.pluginChannel()))
return;
if(plugin.serverName() == null)
return;
ByteArrayInputStream stream = new ByteArrayInputStream(message);
DataInputStream in = new DataInputStream(stream);
try{
String action = in.readUTF();
switch(action){
case "findPlugins" -> sendPluginMessage("hasPlugin", plugin.serverName());
case "parsePlaceholder" -> {
UUID playerUuid = UUID.fromString(in.readUTF());
String text = in.readUTF();
if(!Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")){
sendPluginMessage("parsed", playerUuid.toString(), text);
return;
}
OfflinePlayer targetPlayer = Bukkit.getOfflinePlayer(playerUuid);
String parsed = PlaceholderAPI.setPlaceholders(targetPlayer, text);
sendPluginMessage("parsed", playerUuid.toString(), parsed);
}
default -> plugin.getLogger().warning("Received unknown plugin Message sub channel: " + action);
}
}catch(Exception ex){
plugin.getLogger().log(Level.WARNING, "Encountered an Exception while handling a plugin message.", ex);
}
}
private void sendPluginMessage(String channel, String... values){
plugin.waitUntil(
() -> !Bukkit.getOnlinePlayers().isEmpty() && plugin.serverName() != null,
() -> {
Player plmSender = Bukkit.getOnlinePlayers().iterator().next();
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
DataOutputStream dataOut = new DataOutputStream(outStream);
try{
dataOut.writeUTF(channel);
for(String value : values){
dataOut.writeUTF(value);
}
}catch(Exception ex){
plugin.getLogger().log(Level.WARNING, "Encountered an Exception while writiing data for plugin message.", ex);
}
plmSender.sendPluginMessage(plugin, plugin.pluginChannel(), outStream.toByteArray());
}
);
}
}
@@ -0,0 +1,34 @@
package ch.andre601.placeholderapi.messenger;
import ch.andre601.placeholderapi.ASLPlaceholderAPI;
import org.bukkit.entity.Player;
import org.bukkit.plugin.messaging.PluginMessageListener;
import org.jetbrains.annotations.NotNull;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.util.logging.Level;
public class BungeeCordMessageListener implements PluginMessageListener{
private final ASLPlaceholderAPI plugin;
public BungeeCordMessageListener(ASLPlaceholderAPI plugin){
this.plugin = plugin;
}
@Override
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, byte[] message){
if(!channel.equals("BungeeCord"))
return;
DataInputStream stream = new DataInputStream(new ByteArrayInputStream(message));
try{
String subChannel = stream.readUTF();
if("GetServer".equals(subChannel))
plugin.serverName(stream.readUTF());
}catch(Exception ex){
plugin.getLogger().log(Level.WARNING, "Encountered Exception while reading plugin message from Bungeecord channel.", ex);
}
}
}
@@ -0,0 +1,10 @@
name: ASL-PlaceholderAPI
version: '${plugin.version}'
main: ch.andre601.placeholderapi.ASLPlaceholderAPI
api-version: '1.21'
dependencies:
server:
PlaceholderAPI:
load: BEFORE
authors: [ Andre601 ]
description: Addon to allow PlaceholderAPI placeholders on your Velocity/BungeeCord proxy.
+18 -2
View File
@@ -49,6 +49,10 @@
<id>sonatype</id>
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
</repository>
<repository>
<id>repo-sayandevelopment</id>
<url>https://repo.sayandev.org/snapshots</url>
</repository>
</repositories>
<dependencies>
@@ -69,14 +73,14 @@
<dependency>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-api</artifactId>
<version>1.20-R0.2</version>
<version>1.21-R0.1-SNAPSHOT</version>
<type>jar</type>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-api</artifactId>
<version>1.20-R0.2</version>
<version>1.21-R0.1-SNAPSHOT</version>
<type>javadoc</type>
<scope>provided</scope>
</dependency>
@@ -124,6 +128,18 @@
<version>2.9.0-4</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.sayandev</groupId>
<artifactId>sayanvanish-api</artifactId>
<version>1.6.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.sayandev</groupId>
<artifactId>sayanvanish-proxy-bungeecord</artifactId>
<version>1.6.1</version>
<scope>provided</scope>
</dependency>
<!-- Other -->
<dependency>
@@ -26,8 +26,9 @@
package ch.andre601.advancedserverlist.bungeecord;
import ch.andre601.advancedserverlist.bungeecord.commands.CmdAdvancedServerList;
import ch.andre601.advancedserverlist.bungeecord.listeners.JoinEvent;
import ch.andre601.advancedserverlist.bungeecord.listeners.PingEvent;
import ch.andre601.advancedserverlist.bungeecord.listeners.JoinListener;
import ch.andre601.advancedserverlist.bungeecord.listeners.PingListener;
import ch.andre601.advancedserverlist.bungeecord.listeners.PluginMessageListener;
import ch.andre601.advancedserverlist.bungeecord.logging.BungeeLogger;
import ch.andre601.advancedserverlist.bungeecord.objects.placeholders.BungeePlayerPlaceholders;
import ch.andre601.advancedserverlist.bungeecord.objects.placeholders.BungeeProxyPlaceholders;
@@ -35,9 +36,12 @@ import ch.andre601.advancedserverlist.bungeecord.objects.placeholders.BungeeServ
import ch.andre601.advancedserverlist.core.AdvancedServerList;
import ch.andre601.advancedserverlist.core.interfaces.PluginLogger;
import ch.andre601.advancedserverlist.core.interfaces.core.PluginCore;
import ch.andre601.advancedserverlist.core.objects.PluginMessageUtil;
import ch.andre601.advancedserverlist.core.profiles.handlers.FaviconHandler;
import com.alessiodp.libby.BungeeLibraryManager;
import com.alessiodp.libby.Library;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import de.myzelyam.api.vanish.BungeeVanishAPI;
import net.kyori.adventure.platform.bungeecord.BungeeAudiences;
import net.md_5.bungee.api.Favicon;
@@ -47,6 +51,7 @@ import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Plugin;
import org.bstats.bungeecord.Metrics;
import org.bstats.charts.SimplePie;
import org.sayandev.sayanvanish.bungeecord.api.SayanVanishBungeeAPI;
import java.awt.image.BufferedImage;
import java.nio.file.Path;
@@ -55,9 +60,12 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
public class BungeeCordCore extends Plugin implements PluginCore<Favicon>{
public static final String ASL_PLUGIN_CHANNEL = "advancedserverlist:action";
private final PluginLogger logger = new BungeeLogger(this);
private final Map<String, ServerPing> fetchedServers = new ConcurrentHashMap<>();
@@ -71,12 +79,13 @@ public class BungeeCordCore extends Plugin implements PluginCore<Favicon>{
@Override
public void onLoad(){
logger.info("Loading libraries. This may take a while...");
// Using getLogger() here, because PluginLogger requires MiniMessage, which isn't loaded at this stage.
getLogger().info("Loading libraries. This may take a while...");
successfulLoad = loadLibs();
if(successfulLoad){
logger.info("Library loading completed successfully!");
getLogger().info("Library loading completed successfully!");
}else{
logger.warn("Library loading completed with errors!");
getLogger().warning("Library loading completed with errors!");
}
}
@@ -84,7 +93,7 @@ public class BungeeCordCore extends Plugin implements PluginCore<Favicon>{
public void onEnable(){
if(!successfulLoad){
logger.warn("There were issues during the plugins Loading-phase! Please see the console for possible reasons.");
logger.warn("The plugin won't be enabled to avoid problems.");
logger.warn("Aborting further enabling of the plugin to avoid errors.");
return;
}
@@ -97,17 +106,20 @@ public class BungeeCordCore extends Plugin implements PluginCore<Favicon>{
public void onDisable(){
core.disable();
getProxy().getScheduler().cancel(this);
audiences.close();
}
@Override
public void loadCommands(){
getProxy().getPluginManager().registerCommand(this, new CmdAdvancedServerList(this));
getPluginLogger().success("Registered <white>/advancedserverlist</white>!");
}
@Override
public void loadEvents(){
new JoinEvent(this);
new PingEvent(this);
new JoinListener(this);
new PingListener(this);
new PluginMessageListener(this);
}
@Override
@@ -206,6 +218,9 @@ public class BungeeCordCore extends Plugin implements PluginCore<Favicon>{
// Exclude players when PremiumVanish is enabled and player is hidden.
if(getProxy().getPluginManager().getPlugin("PremiumVanish") != null){
players.removeIf(BungeeVanishAPI::isInvisible);
}else // Do the same if SayanVanish is enabled.
if(getProxy().getPluginManager().getPlugin("SayanVanish") != null){
players.removeIf(player -> SayanVanishBungeeAPI.getInstance().isVanished(player.getUniqueId()));
}
return players.size();
@@ -226,18 +241,24 @@ public class BungeeCordCore extends Plugin implements PluginCore<Favicon>{
return true;
}catch(Exception ex){
logger.warn("Encountered an Exception while trying to load dependencies.", ex);
getLogger().log(Level.WARNING, "Encountered an Exception while trying to load dependencies.", ex);
return false;
}
}
private void fetchServers(){
fetchedServers.clear();
PluginMessageUtil.get().clear();
getProxy().getServers().forEach((name, server) -> server.ping((ping, throwable) -> {
if(throwable != null)
return;
fetchedServers.put(name, ping);
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("findPlugins");
server.sendData(ASL_PLUGIN_CHANNEL, out.toByteArray());
}));
}
}
@@ -32,19 +32,20 @@ import ch.andre601.advancedserverlist.bungeecord.BungeeCordCore;
import ch.andre601.advancedserverlist.bungeecord.objects.impl.BungeePlayerImpl;
import ch.andre601.advancedserverlist.bungeecord.objects.impl.BungeeProxyImpl;
import ch.andre601.advancedserverlist.core.compat.maintenance.MaintenanceUtil;
import ch.andre601.advancedserverlist.core.compat.papi.PAPIUtil;
import ch.andre601.advancedserverlist.core.interfaces.core.PluginCore;
import ch.andre601.advancedserverlist.core.interfaces.events.GenericEventWrapper;
import ch.andre601.advancedserverlist.core.objects.CachedPlayer;
import ch.andre601.advancedserverlist.core.objects.PluginMessageUtil;
import ch.andre601.advancedserverlist.core.parsing.ComponentParser;
import ch.andre601.advancedserverlist.core.profiles.replacer.StringReplacer;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer;
import net.md_5.bungee.api.Favicon;
import net.md_5.bungee.api.ServerPing;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.ProxyPingEvent;
import java.net.InetSocketAddress;
@@ -118,6 +119,11 @@ public class BungeeEventWrapper implements GenericEventWrapper<Favicon, BungeePl
ping.getPlayers().setSample(players);
}
@Override
public void setPlayersHidden(){
ping.getPlayers().setSample(new ServerPing.PlayerInfo[0]);
}
@Override
public void setFavicon(Favicon favicon){
ping.setFavicon(favicon);
@@ -169,25 +175,25 @@ public class BungeeEventWrapper implements GenericEventWrapper<Favicon, BungeePl
@Override
public String parsePAPIPlaceholders(String text, BungeePlayerImpl player){
if(plugin.getProxy().getPluginManager().getPlugin("PAPIProxyBridge") == null)
return text;
if(!PAPIUtil.get().isCompatible())
return text;
String server = PAPIUtil.get().getServer();
if(server == null || server.isEmpty())
return text;
ServerInfo serverInfo = plugin.getProxy().getServerInfo(server);
if(serverInfo == null || serverInfo.getPlayers().isEmpty())
return text;
ProxiedPlayer carrier = PAPIUtil.get().getPlayer(serverInfo.getPlayers());
if(carrier == null)
return text;
return PAPIUtil.get().parse(text, carrier.getUniqueId(), player.getUUID());
return PluginMessageUtil.get().getOrExecute(player.getUUID().toString(), text, (uuid, textValue) -> {
List<String> servers = PluginMessageUtil.get().getKnownServers();
if(servers.isEmpty())
return textValue;
ServerInfo serverInfo = plugin.getProxy().getServerInfo(servers.get(0));
if(serverInfo == null)
return textValue;
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("parse");
out.writeUTF(uuid);
out.writeUTF(textValue);
serverInfo.sendData(BungeeCordCore.ASL_PLUGIN_CHANNEL, out.toByteArray());
PluginMessageUtil.get().queue(uuid, textValue, false);
return PluginMessageUtil.get().getOrDefault(uuid, textValue);
});
}
@Override
@@ -34,10 +34,10 @@ import net.md_5.bungee.event.EventHandler;
import java.net.InetSocketAddress;
public class JoinEvent implements Listener{
public class JoinListener implements Listener{
private final BungeeCordCore plugin;
public JoinEvent(BungeeCordCore plugin){
public JoinListener(BungeeCordCore plugin){
this.plugin = plugin;
plugin.getProxy().getPluginManager().registerListener(plugin, this);
}
@@ -33,11 +33,11 @@ import net.md_5.bungee.api.event.ProxyPingEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
public class PingEvent implements Listener{
public class PingListener implements Listener{
private final BungeeCordCore plugin;
public PingEvent(BungeeCordCore plugin){
public PingListener(BungeeCordCore plugin){
this.plugin = plugin;
plugin.getProxy().getPluginManager().registerListener(plugin, this);
}
@@ -0,0 +1,37 @@
package ch.andre601.advancedserverlist.bungeecord.listeners;
import ch.andre601.advancedserverlist.bungeecord.BungeeCordCore;
import ch.andre601.advancedserverlist.core.objects.PluginMessageUtil;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteStreams;
import net.md_5.bungee.api.event.PluginMessageEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
public class PluginMessageListener implements Listener{
public PluginMessageListener(BungeeCordCore plugin){
plugin.getProxy().getPluginManager().registerListener(plugin, this);
}
@EventHandler
public void onPluginMessage(PluginMessageEvent event){
if(!event.getTag().equals(BungeeCordCore.ASL_PLUGIN_CHANNEL))
return;
ByteArrayDataInput input = ByteStreams.newDataInput(event.getData());
String subChannel = input.readUTF();
switch(subChannel){
case "hasPlugin" -> {
String serverName = input.readUTF();
PluginMessageUtil.get().addServer(serverName);
}
case "parsed" -> {
String uuid = input.readUTF();
String parsed = input.readUTF();
PluginMessageUtil.get().queue(uuid, parsed, true);
}
}
}
}
@@ -27,6 +27,7 @@ package ch.andre601.advancedserverlist.bungeecord.logging;
import ch.andre601.advancedserverlist.bungeecord.BungeeCordCore;
import ch.andre601.advancedserverlist.core.interfaces.PluginLogger;
import ch.andre601.advancedserverlist.core.parsing.ComponentParser;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -55,21 +56,21 @@ public class BungeeLogger implements PluginLogger{
@Override
public void info(String msg, Object... args){
logger.info(String.format(msg, args));
logger.info(ComponentParser.textFormatted(msg, args).toStringStriped());
}
@Override
public void warn(String msg, Object... args){
logger.warning(String.format(msg, args));
logger.warning(ComponentParser.textFormatted(msg, args).toStringStriped());
}
@Override
public void warn(String msg, Throwable throwable){
logger.log(Level.WARNING, msg, throwable);
logger.log(Level.WARNING, ComponentParser.text(msg).toStringStriped(), throwable);
}
@Override
public void warn(String msg, Throwable throwable, Object... args){
logger.log(Level.WARNING, String.format(msg, args), throwable);
logger.log(Level.WARNING, ComponentParser.textFormatted(msg, args).toStringStriped(), throwable);
}
}
+1 -1
View File
@@ -41,7 +41,7 @@
<properties>
<maven.compiler.source>16</maven.compiler.source>
<maven.compiler.target>16</maven.compiler.target>
<plugin.config.version>6</plugin.config.version>
<plugin.config.version>8</plugin.config.version>
</properties>
<repositories>
@@ -69,12 +69,11 @@ public class AdvancedServerList<F>{
placeholders.forEach(this::addPlaceholder);
if(plugin.isPluginEnabled("Maintenance")){
plugin.getPluginLogger().info("Found Maintenance Plugin. Registering extra Placeholder...");
addPlaceholder(new MaintenancePlaceholder(this));
}
plugin.getPluginLogger().info("Completed internal Placeholder registration!");
resolveVersion();
getPlugin().getPluginLogger().info("Starting <white>AdvancedServerList v%s</white>...", version);
load();
}
@@ -119,15 +118,15 @@ public class AdvancedServerList<F>{
}
public void disable(){
getPlugin().getPluginLogger().info("Saving playercache.json file...");
getPlugin().getPluginLogger().info("[-] Saving <white>playercache.json</white> file...");
getPlayerHandler().save();
if(updateChecker != null){
getPlugin().getPluginLogger().info("Disabling Update Checker...");
getPlugin().getPluginLogger().info("[-] Disabling Update Checker...");
updateChecker.disable();
}
getPlugin().getPluginLogger().info("AdvancedServerList disabled!");
getPlugin().getPluginLogger().success("AdvancedServerList disabled!");
}
public void clearFaviconCache(){
@@ -139,70 +138,56 @@ public class AdvancedServerList<F>{
}
private void load(){
printBanner();
resolveVersion();
getPlugin().getPluginLogger().info("Starting AdvancedServerList v%s...", version);
getPlugin().getPluginLogger().info("Platform: %s", plugin.getPlatformInfo());
if(getFileHandler().loadConfig()){
getPlugin().getPluginLogger().info("Successfully loaded config.yml!");
}else{
getPlugin().getPluginLogger().warn("Unable to load config.yml! Check previous lines for errors.");
if(!getFileHandler().loadConfig()){
getPlugin().getPluginLogger().failure("Unable to load <white>config.yml</white>! Check previous lines for errors.");
return;
}
if(getFileHandler().isOldConfig()){
getPlugin().getPluginLogger().info("Detected old config.yml. Attempting to migrate...");
getPlugin().getPluginLogger().info("[-] Detected old <white>config.yml</white>. Attempting to migrate...");
if(getFileHandler().migrateConfig()){
getPlugin().getPluginLogger().info("Migration completed successfully!");
getPlugin().getPluginLogger().success("Migration completed successfully!");
}else{
getPlugin().getPluginLogger().warn("Couldn't migrate config.yml! Check previous lines for errors.");
getPlugin().getPluginLogger().failure("Couldn't migrate <white>config.yml</white>! Check previous lines for errors.");
return;
}
}
if(getFileHandler().getBool(true, "printBanner")){
printBanner();
}
getPlugin().getPluginLogger().info("Platform: <white>%s</white>", plugin.getPlatformInfo());
getPlugin().getPluginLogger().info("");
if(getFileHandler().loadProfiles()){
getPlugin().getPluginLogger().info("Successfully loaded %d profile(s)!", getFileHandler().getProfiles().size());
getPlugin().getPluginLogger().success("Successfully loaded <white>%d</white> profile(s)!", getFileHandler().getProfiles().size());
}else{
getPlugin().getPluginLogger().warn("Unable to load profiles! Check previous lines for errors.");
getPlugin().getPluginLogger().failure("Unable to load profiles! Check previous lines for errors.");
return;
}
getPlugin().loadFaviconHandler(this);
getPlugin().getPluginLogger().info("Loading Commands...");
plugin.loadCommands();
getPlugin().getPluginLogger().info("Commands loaded!");
getPlugin().getPluginLogger().info("Loading events...");
plugin.loadEvents();
getPlugin().getPluginLogger().info("Events loaded!");
getPlugin().getPluginLogger().info("Loading playercache.json...");
getPlayerHandler().load();
getPlugin().getPluginLogger().info("Loading bStats metrics. Disable it in the global config under /plugins/bstats/");
plugin.loadMetrics();
getPlugin().getPluginLogger().info("Metrics loaded!");
getPlugin().getPluginLogger().info("Enabling Schedulers...");
plugin.startScheduler();
getPlugin().getPluginLogger().info("Schedulers enabled.");
getPlugin().getPluginLogger().info("AdvancedServerList is ready!");
getPlugin().getPluginLogger().success("<green>AdvancedServerList is ready!");
if(getFileHandler().getBoolean("checkUpdates"))
this.updateChecker = new UpdateChecker(this);
}
private void printBanner(){
getPlugin().getPluginLogger().info(" _____ _");
getPlugin().getPluginLogger().info(" /\\ / ____| |");
getPlugin().getPluginLogger().info(" / \\ | (___ | |");
getPlugin().getPluginLogger().info(" / /\\ \\ \\___ \\| |");
getPlugin().getPluginLogger().info(" / ____ \\ ____) | |____");
getPlugin().getPluginLogger().info("/_/ \\_\\_____/|______|");
getPlugin().getPluginLogger().info("<#3b90ff> __ _______ ___");
getPlugin().getPluginLogger().info("<#3b90ff> /\\ \\ / ____|_| | |");
getPlugin().getPluginLogger().info("<#3b90ff> / \\ \\ | (_(___ | | |");
getPlugin().getPluginLogger().info("<#3b90ff> / /\\ \\ \\ \\___ \\ \\| | |");
getPlugin().getPluginLogger().info("<#3b90ff> / ____ \\ \\ ____) | | |_|____");
getPlugin().getPluginLogger().info("<#3b90ff>/_/_/ \\_\\_\\_____/_/|______|_|");
getPlugin().getPluginLogger().info("");
}
@@ -219,7 +204,6 @@ public class AdvancedServerList<F>{
}
private void addPlaceholder(PlaceholderProvider placeholderProvider){
getPlugin().getPluginLogger().info("Registering %s Placeholders...", placeholderProvider.getIdentifier());
AdvancedServerList.getApi().addPlaceholderProvider(placeholderProvider);
}
}
@@ -28,7 +28,7 @@ package ch.andre601.advancedserverlist.core.check;
import ch.andre601.advancedserverlist.core.AdvancedServerList;
import ch.andre601.advancedserverlist.core.interfaces.PluginLogger;
import ch.andre601.advancedserverlist.core.interfaces.commands.CmdSender;
import ch.andre601.advancedserverlist.core.objects.ValueCache;
import ch.andre601.advancedserverlist.core.objects.CacheUtil;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
@@ -59,7 +59,7 @@ public class UpdateChecker{
private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(new UpdateCheckThread());
private final ValueCache<CompletableFuture<ModrinthVersion>> cache = new ValueCache<>(Duration.ofMinutes(5));
private final CacheUtil.SingletonCache<CompletableFuture<ModrinthVersion>> cache = new CacheUtil.SingletonCache<>(Duration.ofMinutes(5));
private final AdvancedServerList<?> core;
private final PluginLogger logger;
@@ -123,11 +123,11 @@ public class UpdateChecker{
private void startUpdateChecker(){
executor.scheduleAtFixedRate(() -> {
logger.info("Looking for an update...");
logger.info("[-] Looking for an update...");
performUpdateCheck().whenComplete((version, throwable) -> {
if(version == null || throwable != null){
logger.warn("Failed to look for any updates. Check previous messages for causes.");
logger.failure("Failed to look for any updates. Check previous messages for causes.");
return;
}
@@ -136,9 +136,9 @@ public class UpdateChecker{
int result = version.compare(core.getVersion());
switch(result){
case -1 -> printUpdateBanner(version);
case 0 -> logger.info("You are running the latest version!");
case 0 -> logger.success("You are running the latest version!");
case 1 -> logger.info(
"Your version (%s) seems to be newer than the latest release (%s). Are you running a dev build?",
"[?] Your version (<white>%s</white>) seems to be newer than the latest release (<white>%s</white>). Are you running a dev build?",
core.getVersion(), version.versionNumber()
);
}
@@ -185,18 +185,18 @@ public class UpdateChecker{
logger.warn("=======================================================================");
logger.warn("You are running an outdated version of AdvancedServerList!");
logger.warn("");
logger.warn("Your version: %s", core.getVersion());
logger.warn("Modrinth: %s", version.versionNumber());
logger.warn("Your version: <white>%s</white>", core.getVersion());
logger.warn("Modrinth: <white>%s</white>", version.versionNumber());
logger.warn("");
if(!version.isRelease()){
logger.warn("WARNING!");
logger.warn("<bold>WARNING!</bold>");
logger.warn("This is a %s version and may contain breaking changes and/or bugs!", version.versionType());
logger.warn("");
}
logger.warn("Download the latest version from here:");
logger.warn("https://modrinth.com/plugin/advancedserverlist/version/%s", version.id());
logger.warn("<white>https://modrinth.com/plugin/advancedserverlist/version/%s</white>", version.id());
logger.warn("=======================================================================");
}
@@ -102,43 +102,27 @@ public class CommandHandler{
public void handle(CmdSender sender, String[] args){
sender.sendPrefixedMsg("- Commands. Hover for details.");
for(PluginCommand command : core.getCommandHandler().getSubCommands()){
sender.sendPrefixedMsg("");
sender.sendPrefixedMsg(
"<aqua><hover:show_text:\"%s\">" +
sender.sendMsg();
sender.sendMsg(
"<click:suggest_command:/asl %s>" +
"/asl <white>%s</white>" +
"</click>" +
"</hover></aqua>",
getCommandHover(command),
command.name(),
command.usage() +
"</click>",
command.name()
);
sender.sendMsg("<grey>[<hover:show_text:\"%s\"><white>Permission</white></hover>]</grey>", command.permission());
}
}
@Override
public String usage(){
return "<aqua>/asl <white>help</white></aqua>";
return new CommandStringBuilder("help", description())
.build();
}
@Override
public String description(){
return "Shows all available commands.";
}
private String getCommandHover(PluginCommand command){
return String.format(
"""
<grey>Usage: %s
Permission: <white>%s</white>
<white>%s</white>
""",
command.usage(),
command.permission(),
command.description()
);
}
}
private static class Reload extends PluginCommand{
@@ -172,7 +156,8 @@ public class CommandHandler{
@Override
public String usage(){
return "<aqua>/asl <white>reload</white></aqua>";
return new CommandStringBuilder("reload", description())
.build();
}
@Override
@@ -206,7 +191,8 @@ public class CommandHandler{
@Override
public String usage(){
return "<aqua>/asl <white>clearcache</white></aqua>";
return new CommandStringBuilder("clearcache", description())
.build();
}
@Override
@@ -268,7 +254,18 @@ public class CommandHandler{
@Override
public String usage(){
return "<aqua>/asl <white>migrate <grey>\\<</grey>Plugin<grey>></grey></white></aqua>";
return new CommandStringBuilder("migrate", description())
.appendSpace()
.appendArgument(new CommandStringBuilder.ArgumentBuilder("Plugin").description(
"""
The Plugin to migrate from.
Supported options:
- <white>ServerListPlus</white>
- <white>MiniMOTD</white>
"""
).build())
.build();
}
@Override
@@ -360,7 +357,7 @@ public class CommandHandler{
sender.sendErrorMsg("<red>There was an error while creating the file</red> %s<red>!", copy);
sender.sendErrorMsg("<red>Check console for further details.");
core.getPlugin().getPluginLogger().warn("Encountered IOException while creating file %s", ex, copy);
core.getPlugin().getPluginLogger().warn("Encountered IOException while creating file <white>%s</white>", ex, copy);
return;
}
@@ -378,7 +375,7 @@ public class CommandHandler{
sender.sendErrorMsg("<red>There was an error while loading the file</red> %s<red>!", copy);
sender.sendErrorMsg("<red>Check console for further details.");
core.getPlugin().getPluginLogger().warn("Encountered IOException while loading file %s", ex, copy);
core.getPlugin().getPluginLogger().warn("Encountered IOException while loading file <white>%s</white>", ex, copy);
return;
}
@@ -398,7 +395,7 @@ public class CommandHandler{
sender.sendErrorMsg("<red>There was an Error while copying values from</red> %s <red>to</red> %s<red>.", name, copy);
sender.sendErrorMsg("<red>Check console for details.");
core.getPlugin().getPluginLogger().warn("Encountered SerializationException while set values for %s!", ex, copy);
core.getPlugin().getPluginLogger().warn("Encountered SerializationException while set values for <white>%s</white>!", ex, copy);
return;
}
@@ -410,7 +407,7 @@ public class CommandHandler{
sender.sendErrorMsg("<red>There was an Error while trying to save</red> %s<red>!", copy);
sender.sendErrorMsg("<red>Check console for details.");
core.getPlugin().getPluginLogger().warn("Encountered IOException while saving ConfigurationNode for %s!", ex, copy);
core.getPlugin().getPluginLogger().warn("Encountered IOException while saving ConfigurationNode for <white>%s</white>!", ex, copy);
}
}
default -> {
@@ -422,11 +419,32 @@ public class CommandHandler{
@Override
public String usage(){
return "<aqua>/asl <white>profiles</white> <grey>\\<" +
"<white>add</white> \\<<white>name</white>> | " +
"<white>copy</white> \\<<white>profile</white>> \\<<white>name</white>> | " +
"<white>list</white>" +
"></grey></aqua>";
return new CommandStringBuilder("profiles", "Creates a new profile, copies one from an existing file, or lists all available profiles.")
.appendSpace()
.appendText("<grey>\\<")
// add <name>
.appendLiteralArgument("add", "Adds a new profile using the default values.")
.appendSpace()
.appendArgument(new CommandStringBuilder.ArgumentBuilder("name").description("Name of the new profile").required().build())
// Divider
.appendSpace()
.appendText("|")
.appendSpace()
// copy <profile> <name>
.appendLiteralArgument("copy", "Creates a copy of the provided profile.")
.appendSpace()
.appendArgument(new CommandStringBuilder.ArgumentBuilder("profile").description("The profile to copy.").required().build())
.appendSpace()
.appendArgument(new CommandStringBuilder.ArgumentBuilder("name").description("Name of the copy.").required().build())
// Divider
.appendSpace()
.appendText("|")
.appendSpace()
// list
.appendLiteralArgument("list", "Lists all currently know profiles and whether they are valid or not.")
// Closing bracket
.appendText("></grey>")
.build();
}
@Override
@@ -0,0 +1,110 @@
/*
* MIT License
*
* Copyright (c) 2022-2025 Andre_601
*
* 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 ch.andre601.advancedserverlist.core.commands;
import java.util.ArrayList;
import java.util.List;
public class CommandStringBuilder{
private final List<CommandPart> commandParts = new ArrayList<>();
public CommandStringBuilder(String subcommand, String description){
appendLiteralArgument(subcommand, description);
}
public CommandStringBuilder appendArgument(Argument argument){
this.commandParts.add(argument);
return this;
}
public CommandStringBuilder appendText(String text){
this.commandParts.add(new Text(text));
return this;
}
public CommandStringBuilder appendLiteralArgument(String name, String description){
return appendText("<hover:show_text:\"<grey>" + description + "</grey>\"><white>" + name + "</white></hover>");
}
public CommandStringBuilder appendSpace(){
return appendText(" ");
}
public String build(){
StringBuilder builder = new StringBuilder("<aqua>/asl ");
for(CommandPart part : commandParts){
builder.append(part.build());
}
builder.append("</aqua>"); // Closing the first color tag.
return builder.toString();
}
public static class ArgumentBuilder{
private final String name;
private String description = "No description provided.";
private boolean required = false;
public ArgumentBuilder(String name){
this.name = name;
}
public ArgumentBuilder description(String description){
this.description = description;
return this;
}
public ArgumentBuilder required(){
this.required = true;
return this;
}
public Argument build(){
return new Argument(name, description, required);
}
}
private record Text(String text) implements CommandPart{
@Override
public String build(){
return text();
}
}
private record Argument(String name, String description, boolean required) implements CommandPart{
@Override
public String build(){
return "<hover:show_text:\"<grey>" + description() + "</grey>\">" +
"<grey>" + (required() ? "\\<" : "[") + "<white>" + name() + "</white>" + (required() ? ">" : "]") +
"</grey></hover>";
}
}
private interface CommandPart{
String build();
}
}
@@ -1,92 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2022-2023 Andre_601
*
* 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 ch.andre601.advancedserverlist.core.compat.papi;
import ch.andre601.advancedserverlist.core.objects.ValueCache;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import net.william278.papiproxybridge.api.PlaceholderAPI;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class PAPIUtil{
private static PAPIUtil instance;
private final ValueCache<CompletableFuture<Set<String>>> serversCache = new ValueCache<>(Duration.ofSeconds(10));
private final Cache<UUID, CompletableFuture<String>> textCache = CacheBuilder.newBuilder()
.expireAfterWrite(Duration.ofSeconds(10))
.build();
private final PlaceholderAPI papi;
private PAPIUtil(){
this.papi = PlaceholderAPI.createInstance();
}
public static PAPIUtil get(){
if(instance != null)
return instance;
return (instance = new PAPIUtil());
}
// Make sure the version of PAPIProxyBridge we get has the required methods we need...
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public boolean isCompatible(){
try{
papi.getClass().getMethod("getServers");
return true;
}catch(NoSuchMethodException ex){
return false;
}
}
public String getServer(){
Set<String> values = serversCache.get(papi::getServers).getNow(Collections.emptySet());
if(values.isEmpty())
return null;
return List.copyOf(values).get(0);
}
public <P> P getPlayer(Collection<P> players){
if(players.isEmpty())
return null;
return List.copyOf(players).get(0);
}
public String parse(String text, UUID carrier, UUID player){
try{
return textCache.get(player, () -> papi.formatPlaceholders(text, carrier, player)).getNow(text);
}catch(ExecutionException ex){
return text;
}
}
}
@@ -158,7 +158,7 @@ public class PingEventHandler{
boolean hidePlayers = ProfileManager.checkOption(entry.hidePlayersEnabled());
if(hidePlayers && ignoreMaintenance(event, "hidePlayers")){
if(hidePlayers && ignoreMaintenance(event, "hidePlayers") && ignoreMaintenance(event, "hidePlayersHover")){
logger.debug(PingEventHandler.class, "'playerCount -> hidePlayers' enabled. Hiding player count...");
event.hidePlayers();
@@ -175,8 +175,14 @@ public class PingEventHandler{
);
}
if(ProfileManager.checkOption(entry.players()) && !hidePlayers && ignoreMaintenance(event, "playerCountHover")){
logger.debug(PingEventHandler.class, "'playerCount -> hover' option set. Applying '%s'...", String.join("\\n", entry.players()));
boolean hidePlayersHover = ProfileManager.checkOption(entry.hidePlayersHoverEnabled());
if(hidePlayersHover){
logger.debug(PingEventHandler.class, "'playerCount -> hidePlayersHover' option set. Hiding Player Hover...");
event.setPlayersHidden();
}
if(ProfileManager.checkOption(entry.players()) && !hidePlayers && !hidePlayersHover && ignoreMaintenance(event, "playerCountHover")){
logger.debug(PingEventHandler.class, "'playerCount -> hover' option set. Applying ['%s']...", String.join("', '", entry.players()));
event.setPlayers(entry.players(), player, server);
}
@@ -46,7 +46,9 @@ public class ConfigMigrator{
public static ConfigurationTransformation.Versioned create(){
return ConfigurationTransformation.versionedBuilder()
.versionKey("configVersion")
.addVersion(LATEST, fiveToSix())
.addVersion(LATEST, sevenToEight())
.addVersion(7, sixToSeven())
.addVersion(6, fiveToSix())
.addVersion(5, fourToFive())
.addVersion(4, threeToFour())
.addVersion(3, twoToThree())
@@ -54,6 +56,26 @@ public class ConfigMigrator{
.build();
}
public static ConfigurationTransformation sevenToEight(){
return ConfigurationTransformation.builder()
.addAction(NodePath.path("disableDuringMaintenance"), (path, value) -> {
value.node("hidePlayersHover").set(true);
return null;
})
.build();
}
public static ConfigurationTransformation sixToSeven(){
return ConfigurationTransformation.builder()
.addAction(NodePath.path(), (path, value) -> {
value.node("printBanner").set(true);
return null;
})
.build();
}
public static ConfigurationTransformation fiveToSix(){
return ConfigurationTransformation.builder()
.addAction(NodePath.path("disableDuringMaintenance"), (path, value) -> {
@@ -122,7 +144,7 @@ public class ConfigMigrator{
versioned.apply(node);
final int endVersion = versioned.version(node);
if(startVersion < endVersion)
logger.info("Migrated config.yml from v" + startVersion + " to v" + endVersion);
logger.success("Successfully migrated config from <white>v%d</white> to <white>v%d</white>", startVersion, endVersion);
}
return node;
@@ -72,7 +72,6 @@ public class FileHandler{
}
public boolean loadConfig(){
logger.info("Loading config.yml...");
File folder = plugin.getPlugin().getFolderPath().toFile();
if(!folder.exists() && !folder.mkdirs()){
logger.warn("Couldn't create folder for plugin. Is it missing Write permissions?");
@@ -87,7 +86,7 @@ public class FileHandler{
}
Files.copy(stream, config);
logger.info("Created new config.yml!");
logger.success("Created new config.yml!");
}catch(IOException ex){
logger.warn("Cannot create config.yml for plugin.", ex);
return false;
@@ -98,7 +97,7 @@ public class FileHandler{
}
public boolean loadProfiles(){
logger.info("Loading profiles...");
logger.info("[-] Loading profiles...");
if(createFile("default.yml")){
return reloadProfiles();
@@ -109,7 +108,7 @@ public class FileHandler{
public boolean createFile(String name){
if(Files.notExists(profilesFolder) && profilesFolder.toFile().mkdirs()){
logger.info("Successfully created profiles folder.");
logger.debugSuccess(FileHandler.class, "Successfully created profiles folder.");
}
File file = new File(profilesFolder.toFile(), name);
@@ -126,7 +125,7 @@ public class FileHandler{
Files.copy(stream, profilesFolder.resolve(name));
return true;
}catch(IOException ex){
logger.warn("Encountered IOException while trying to create file %s", ex, name);
logger.warn("Encountered IOException while trying to create file <white>%s</white>", ex, name);
return false;
}
}
@@ -160,7 +159,7 @@ public class FileHandler{
File[] files = profilesFolder.toFile().listFiles(((dir, name) -> name.endsWith(".yml")));
if(files == null || files.length == 0){
logger.warn("Cannot load files from profiles folder! No valid YAML files present.");
logger.failure("Cannot load files from profiles folder! No valid YAML files present.");
return false;
}
@@ -170,11 +169,11 @@ public class FileHandler{
continue;
profiles.add(ServerListProfile.Builder.resolve(file.getName(), tmp, logger).build());
logger.info("Loaded '%s'!", file.getName());
logger.debugSuccess(FileHandler.class, "Loaded '<white>%s</white>'!", file.getName());
}
if(profiles.isEmpty()){
logger.warn("Couldn't load any profile from profiles folder!");
logger.failure("Couldn't load any profile from profiles folder!");
return false;
}
@@ -191,7 +190,7 @@ public class FileHandler{
try{
return loader.load();
}catch(IOException ex){
logger.warn("Cannot load '%s' due to an IOException!", ex, path.toFile().getName());
logger.warn("Cannot load '<white>%s</white>' due to an IOException!", ex, path.toFile().getName());
return null;
}
}
@@ -218,11 +217,11 @@ public class FileHandler{
}
private boolean makeBackup(){
logger.info("Making backup of old config.yml...");
logger.info("[-] Making backup of old config.yml...");
File backups = plugin.getPlugin().getFolderPath().resolve("backups").toFile();
if(!backups.exists() && !backups.mkdirs()){
logger.warn("Cannot create backups folder for migration!");
logger.failure("Cannot create backups folder for migration!");
return false;
}
@@ -231,16 +230,16 @@ public class FileHandler{
File configBackup = new File(backups, "config_" + date.replace(":", "_") + ".yml");
try{
if(!configBackup.exists() && !configBackup.createNewFile()){
logger.warn("Cannot create backup file for config.yml!");
logger.failure("Cannot create backup file for config.yml!");
return false;
}
Files.copy(config, configBackup.toPath(), StandardCopyOption.REPLACE_EXISTING);
logger.info("Saved backup as '%s'!", configBackup.getName());
logger.success("Saved backup as '<white>%s</white>'!", configBackup.getName());
return true;
}catch(IOException ex){
logger.warn("Encountered IOException while trying to create a backup.", ex);
logger.failure("Encountered IOException while trying to create a backup.", ex);
return false;
}
}
@@ -27,10 +27,18 @@ package ch.andre601.advancedserverlist.core.interfaces;
public interface PluginLogger{
default void debugSuccess(Class<?> clazz, String msg, Object... args){
debug(clazz, "[<green>✓</green>] " + msg, args);
}
void debug(Class<?> clazz, String msg, Object... args);
void debugWarn(Class<?> clazz, String msg, Object... args);
default void success(String msg, Object... args){
info("[<green>✓</green>] " + msg, args);
}
void info(String msg, Object... args);
void warn(String msg, Object... args);
@@ -38,4 +46,8 @@ public interface PluginLogger{
void warn(String msg, Throwable throwable);
void warn(String msg, Throwable throwable, Object... args);
default void failure(String msg, Object... args){
warn("[<red>x</red>] " + msg, args);
}
}
@@ -52,6 +52,8 @@ public interface GenericEventWrapper<F, P extends GenericPlayer>{
void setPlayers(List<String> players, P player, GenericServer server);
void setPlayersHidden();
void setFavicon(F favicon);
void setDefaultFavicon();
@@ -56,7 +56,7 @@ public class MiniMOTDConfigMigrator{
Path mainConf = core.getPlugin().getFolderPath().getParent().resolve(folder).resolve("main.conf");
if(!Files.exists(mainConf)){
logger.warn("[Migrator - MiniMOTD] Cannot find a main.conf file in /plugins/%s/ folder.", folder);
logger.warn("[<white>Migrator - MiniMOTD</white>] Cannot find a main.conf file in <white>/plugins/%s/</white> folder.", folder);
sender.sendErrorMsg(" -> <red>No main.conf file found.");
return false;
}
@@ -76,13 +76,13 @@ public class MiniMOTDConfigMigrator{
config = node.get(MiniMOTDConfig.class);
}catch(IOException ex){
logger.warn("[Migrator - MiniMOTD] Encountered IOException while loading main.conf file.", ex);
logger.warn("[<white>Migrator - MiniMOTD</white>] Encountered IOException while loading main.conf file.", ex);
sender.sendErrorMsg(" -> <red>IOException while loading</red> main.conf <red>file.");
return false;
}
if(config == null){
logger.warn("[Migrator - MiniMOTD] Retrieved main.conf file returned null.");
logger.warn("[<white>Migrator - MiniMOTD</white>] Retrieved main.conf file returned null.");
sender.sendErrorMsg(" -> <red>Received invalid</red> main.conf <red>file.");
return false;
}
@@ -147,14 +147,14 @@ public class MiniMOTDConfigMigrator{
boolean profilesInvalid = profiles.isEmpty() || profiles.stream().allMatch(ProfileEntry::isInvalid);
if(entry.isInvalid() && profilesInvalid){
logger.warn("[Migrator - MiniMOTD] Main ProfileEntry was invalid and profiles were empty.");
logger.warn("[<white>Migrator - MiniMOTD</white>] Main ProfileEntry was invalid and profiles were empty.");
sender.sendErrorMsg(" -> <red>Received invalid configuration.");
return false;
}
Path profile = core.getPlugin().getFolderPath().resolve("profiles").resolve("minimotd_migrated.yml");
if(Files.exists(profile)){
logger.warn("[Migrator - MiniMOTD] Cannot create new file minimotd_migrated.yml. One with the same name is already present.");
logger.warn("[<white>Migrator - MiniMOTD</white>] Cannot create new file minimotd_migrated.yml. One with the same name is already present.");
sender.sendErrorMsg(" -> <red>File</red> minimotd_migrated.yml <red>already present.");
return false;
@@ -163,7 +163,7 @@ public class MiniMOTDConfigMigrator{
try{
Files.createFile(profile);
}catch(IOException ex){
logger.warn("[Migrator - MiniMOTD] Encountered an IOException while trying to create file minimotd_migrated.yml.", ex);
logger.warn("[<white>Migrator - MiniMOTD</white>] Encountered an IOException while trying to create file minimotd_migrated.yml.", ex);
sender.sendErrorMsg(" -> <red>File creation error.");
return false;
@@ -180,14 +180,14 @@ public class MiniMOTDConfigMigrator{
try{
migrated = yamlLoader.load();
}catch(IOException ex){
logger.warn("[Migrator - MiniMOTD] Encountered an IOException while trying to load file minimotd_migrated.yml.", ex);
logger.warn("[<white>Migrator - MiniMOTD</white>] Encountered an IOException while trying to load file minimotd_migrated.yml.", ex);
sender.sendErrorMsg(" -> <red>File loading error.");
return false;
}
if(migrated == null){
logger.warn("[Migrator - MiniMOTD] Cannot migrate Configuration. ConfigurationNode was null.");
logger.warn("[<white>Migrator - MiniMOTD</white>] Cannot migrate Configuration. ConfigurationNode was null.");
sender.sendErrorMsg(" -> <red>File loading error.");
return false;
@@ -202,7 +202,7 @@ public class MiniMOTDConfigMigrator{
migrated.set(entry);
}catch(SerializationException ex){
logger.warn("[Migrator - MiniMOTD] Encountered a SerializationException while setting values.", ex);
logger.warn("[<white>Migrator - MiniMOTD</white>] Encountered a SerializationException while setting values.", ex);
sender.sendErrorMsg(" -> <red>Error while updating file</red> minimotd_migrated.yml<red>.");
return false;
@@ -214,7 +214,7 @@ public class MiniMOTDConfigMigrator{
return true;
}catch(IOException ex){
logger.warn("[Migrator - MiniMOTD] Encountered an IOException while trying to save new file minimotd_migrated.yml.", ex);
logger.warn("[<white>Migrator - MiniMOTD</white>] Encountered an IOException while trying to save new file minimotd_migrated.yml.", ex);
sender.sendErrorMsg(" -> <red>File saving error.");
return false;
@@ -101,13 +101,13 @@ public class SLPConfigMigrator{
ServerListPlusCore slpCore = ServerListPlusCore.getInstance();
PluginLogger logger = core.getPlugin().getPluginLogger();
if(slpCore == null){
logger.warn("[Migrator - ServerListPlus] Cannot migrate ServerListPlus config. ServerListPlus is not active!");
logger.warn("[<white>Migrator - ServerListPlus</white>] Cannot migrate ServerListPlus config. ServerListPlus is not active!");
return 0;
}
ServerStatusConf conf = slpCore.getConf(ServerStatusConf.class);
if(conf == null){
logger.warn("[Migrator - ServerListPlus] Cannot migrate ServerListPlus configuration. Received configuration was null.");
logger.warn("[<white>Migrator - ServerListPlus</white>] Cannot migrate ServerListPlus configuration. Received configuration was null.");
return 0;
}
@@ -130,7 +130,7 @@ public class SLPConfigMigrator{
private static int parseConf(AdvancedServerList<?> core, CmdSender sender, PersonalizedStatusConf.StatusConf conf, Type type){
PluginLogger logger = core.getPlugin().getPluginLogger();
if(conf == null){
logger.info("[Migrator - ServerListPlus] No StatusConf found for type %s. Skipping...", type.getName());
logger.info("[<white>Migrator - ServerListPlus</white>] No StatusConf found for type <white>%s</white>. Skipping...", type.getName());
sender.sendPrefixedMsg(" -> Not found! Skipping...");
return 0;
@@ -170,7 +170,7 @@ public class SLPConfigMigrator{
boolean profilesInvalid = profileEntries.isEmpty() || profileEntries.stream().anyMatch(ProfileEntry::isInvalid);
if(entry.isInvalid() && profilesInvalid){
logger.warn("[Migrator - ServerListPlus] Unable to parse ServerListPlus configuration of type %s. Generated ProfileEntry was invalid.", type.getName());
logger.warn("[<white>Migrator - ServerListPlus</white>] Unable to parse ServerListPlus configuration of type <white>%s</white>. Generated ProfileEntry was invalid.", type.getName());
sender.sendErrorMsg(" -> <red>Received invalid Configuration.");
return 0;
@@ -178,7 +178,7 @@ public class SLPConfigMigrator{
Path profile = core.getPlugin().getFolderPath().resolve("profiles").resolve(type.getFile());
if(Files.exists(profile)){
logger.warn("[Migrator - ServerListPlus] Cannot create new file %s. One with the same name is already present", type.getFile());
logger.warn("[<white>Migrator - ServerListPlus</white>] Cannot create new file <white>%s</white>. One with the same name is already present", type.getFile());
sender.sendErrorMsg(" -> <red>File</red> %s <red>already present.", type.getFile());
return 0;
@@ -187,7 +187,7 @@ public class SLPConfigMigrator{
try{
Files.createFile(profile);
}catch(IOException ex){
logger.warn("[Migrator - ServerListPlus] Encountered an IOException while trying to create file %s.", ex, type.getFile());
logger.warn("[<white>Migrator - ServerListPlus</white>] Encountered an IOException while trying to create file <white>%s</white>.", ex, type.getFile());
sender.sendErrorMsg(" -> <red>File creation error.");
return 0;
@@ -204,14 +204,14 @@ public class SLPConfigMigrator{
try{
node = loader.load();
}catch(IOException ex){
logger.warn("[Migrator - ServerListPlus] Encountered an IOException while trying to load file %s.", ex, type.getFile());
logger.warn("[<white>Migrator - ServerListPlus</white>] Encountered an IOException while trying to load file <white>%s</white>.", ex, type.getFile());
sender.sendErrorMsg(" -> <red>File loading error.");
return 0;
}
if(node == null){
logger.warn("[Migrator - ServerListPlus] Cannot migrate Configuration of type %s. ConfigurationNode was null.", type.getName());
logger.warn("[<white>Migrator - ServerListPlus</white>] Cannot migrate Configuration of type <white>%s</white>. ConfigurationNode was null.", type.getName());
sender.sendErrorMsg(" -> <red>File loading error.");
return 0;
@@ -236,7 +236,7 @@ public class SLPConfigMigrator{
node.set(entry);
}catch(SerializationException ex){
logger.warn("[Migrator - ServerListPlus] Encountered a SerializationException while setting values.", ex);
logger.warn("[<white>Migrator - ServerListPlus</white>] Encountered a SerializationException while setting values.", ex);
sender.sendErrorMsg(" -> <red>Error while updating file</red> %s<red>.", type.getFile());
return 0;
@@ -248,7 +248,7 @@ public class SLPConfigMigrator{
return 1;
}catch(IOException ex){
logger.warn("[Migrator - ServerListPlus] Encountered an IOException while trying to save new file %s.", ex, type.getFile());
logger.warn("[<white>Migrator - ServerListPlus</white>] Encountered an IOException while trying to save new file <white>%s</white>.", ex, type.getFile());
sender.sendErrorMsg(" -> <red>File saving error.");
return 0;
@@ -26,25 +26,58 @@
package ch.andre601.advancedserverlist.core.objects;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
public class ValueCache<T>{
public abstract class CacheUtil{
private final Duration duration;
private CacheValue<T> cache = null;
protected final Duration duration;
public ValueCache(Duration duration){
public CacheUtil(Duration duration){
this.duration = duration;
}
public T get(Supplier<T> supplier){
if(cache == null || cache.expired())
cache = new CacheValue<>(System.currentTimeMillis(), duration, supplier.get());
public static class SingletonCache<T> extends CacheUtil{
return cache.value();
private CacheValue<T> cacheValue = null;
public SingletonCache(Duration duration){
super(duration);
}
public T get(Supplier<T> supplier){
if(cacheValue == null || cacheValue.expired())
cacheValue = new CacheValue<>(System.currentTimeMillis(), duration, supplier.get());
return cacheValue.value();
}
}
private record CacheValue<T>(long timestamp, Duration duration, T value){
public static class IdentifiableCache<K, V> extends CacheUtil{
private final Map<K, CacheValue<V>> cacheValues = new HashMap<>();
public IdentifiableCache(Duration duration){
super(duration);
}
public V get(K key, Supplier<V> supplier){
CacheValue<V> value = cacheValues.get(key);
if(value == null || value.expired()){
value = new CacheValue<>(System.currentTimeMillis(), duration, supplier.get());
cacheValues.put(key, value);
}
return cacheValues.get(key).value();
}
public void clear(){
cacheValues.clear();
}
}
public record CacheValue<T>(long timestamp, Duration duration, T value){
public boolean expired(){
return (timestamp < 0L) || System.currentTimeMillis() - timestamp >= duration.toMillis();
}
@@ -0,0 +1,57 @@
package ch.andre601.advancedserverlist.core.objects;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.BiFunction;
public class PluginMessageUtil{
private static PluginMessageUtil instance;
private final List<String> knownServers = new CopyOnWriteArrayList<>();
private final Map<String, ParsedText> text = new ConcurrentHashMap<>();
private PluginMessageUtil(){}
public static PluginMessageUtil get(){
if(instance != null)
return instance;
return (instance = new PluginMessageUtil());
}
public void addServer(String name){
this.knownServers.add(name);
}
public List<String> getKnownServers(){
return knownServers;
}
public void clear(){
knownServers.clear();
}
public String getOrExecute(String key, String value, BiFunction<String, String, String> function){
if(text.get(key) != null){
return getOrDefault(key, value);
}
return function.apply(key, value);
}
public void queue(String key, String value, boolean parsed){
text.put(key, new ParsedText(value, parsed));
}
public String getOrDefault(String key, String def){
if(text.get(key) == null)
return def;
return text.get(key).parsed() ? text.remove(key).text() : text.get(key).text();
}
private record ParsedText(String text, boolean parsed){}
}
@@ -51,8 +51,13 @@ public class ComponentParser{
return new ComponentParser(text);
}
public static ComponentParser textFormatted(String text, Object... args){
return text(String.format(text, args));
}
public ComponentParser modifyText(Function<String, String> function){
this.text = function.apply(text);
return this;
}
@@ -68,4 +73,8 @@ public class ComponentParser{
public String toString(){
return legacy.serialize(toComponent());
}
public String toStringStriped(){
return mm.stripTags(text);
}
}
@@ -89,7 +89,7 @@ public class TextCenterUtil{
}catch(IOException ex){
plugin.getPlugin().getPluginLogger().warn("Encountered IOException while loading font widths.");
plugin.getPlugin().getPluginLogger().warn("The <center> Placeholder won't work.");
plugin.getPlugin().getPluginLogger().warn("Cause: %s", ex.getMessage());
plugin.getPlugin().getPluginLogger().warn("Cause: <white>%s</white>", ex.getMessage());
return new HashMap<>();
}
}
@@ -51,13 +51,13 @@ public record ServerListProfile(int priority, String condition, ProfileEntry def
boolean result = core.getParser().evaluate(condition, player, server, collector);
if(collector.hasWarnings()){
logger.warn("Encountered %d Error(s) while parsing condition for file '%s':", collector.getWarnings().size(), file);
logger.warn("Encountered <white>%d</white> Error(s) while parsing condition for file '<white>%s</white>':", collector.getWarnings().size(), file);
for(ParseWarnCollector.Context context : collector.getWarnings()){
logger.warn(" - At position %s:", context.position() == -1 ? "Unknown" : context.position());
logger.warn(" %s", condition);
logger.warn(" ".repeat(context.position() + 3) + "^");
logger.warn(" -> %s", context.message());
logger.warn(" - At position <white>%s</white>:", context.position() == -1 ? "<italic>Unknown</italic>" : context.position());
logger.warn(" <white>%s</white>", condition);
logger.warn(" ".repeat(context.position() + 3) + "<red>^</red>");
logger.warn(" <grey>-></grey> %s", context.message());
}
}
@@ -146,7 +146,7 @@ public record ServerListProfile(int priority, String condition, ProfileEntry def
try{
this.profiles = node.node("profiles").getList(ProfileEntry.class);
}catch(SerializationException ex){
logger.warn("Encountered a SerializationException while resolving the profiles entry for %s", ex, fileName);
logger.warn("Encountered a SerializationException while resolving the profiles entry for <white>%s</white>", ex, fileName);
}
return this;
@@ -159,7 +159,7 @@ public record ServerListProfile(int priority, String condition, ProfileEntry def
try{
this.defaultProfile = node.get(ProfileEntry.class);
}catch(SerializationException ex){
logger.warn("Encountered a SerializationException while resolving the global profile for %s", ex, fileName);
logger.warn("Encountered a SerializationException while resolving the global profile for <white>%s</white>", ex, fileName);
}
return this;
}
@@ -27,8 +27,7 @@ package ch.andre601.advancedserverlist.core.profiles.handlers;
import ch.andre601.advancedserverlist.core.AdvancedServerList;
import ch.andre601.advancedserverlist.core.interfaces.PluginLogger;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import ch.andre601.advancedserverlist.core.objects.CacheUtil;
import javax.imageio.ImageIO;
import java.awt.*;
@@ -42,9 +41,13 @@ import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.List;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
public class FaviconHandler<F>{
@@ -52,7 +55,7 @@ public class FaviconHandler<F>{
private final AdvancedServerList<F> core;
private final PluginLogger logger;
private final ThreadPoolExecutor faviconThreadPool;
private final Cache<String, CompletableFuture<F>> faviconCache;
private final CacheUtil.IdentifiableCache<String, CompletableFuture<F>> faviconCache;
private final Map<String, F> localFavicons = new HashMap<>();
private final HttpClient client = HttpClient.newHttpClient();
@@ -62,42 +65,32 @@ public class FaviconHandler<F>{
this.core = core;
this.logger = core.getPlugin().getPluginLogger();
this.faviconThreadPool = createFaviconThreadPool();
this.faviconCache = CacheBuilder.newBuilder()
.expireAfterWrite(core.getFileHandler().getLong(1, 1, "faviconCacheTime"), TimeUnit.MINUTES)
.build();
this.faviconCache = new CacheUtil.IdentifiableCache<>(Duration.ofMinutes(
core.getFileHandler().getLong(1, 1, "faviconCacheTime")
));
loadLocalFavicons();
}
public F getFavicon(String input){
if(localFavicons.size() > 0){
if(!localFavicons.isEmpty()){
logger.debug(FaviconHandler.class, "Current Local Favicons:");
for(String key : localFavicons.keySet()){
logger.debug(FaviconHandler.class, " - %s", key);
}
}
if(input.equalsIgnoreCase("random")){
logger.debug(FaviconHandler.class, "Input matches 'random'. Returning random Favicon...");
return getRandomized();
}
if(localFavicons.containsKey(input.toLowerCase(Locale.ROOT))){
logger.debug(FaviconHandler.class, "Input matches local favicon Image. Returning it...");
return localFavicons.get(input.toLowerCase(Locale.ROOT));
}
try{
logger.debug(FaviconHandler.class, "Getting Favicon image from cache...");
return faviconCache.get(input, () -> getFuture(input)).getNow(null);
}catch(ExecutionException ex){
logger.warn("Received ExecutionException while retrieving Favicon for '%s'.", ex, input);
return null;
}
logger.debug(FaviconHandler.class, "Getting Favicon image from cache...");
return faviconCache.get(input, () -> getFuture(input)).getNow(null);
}
public void cleanCache(){
faviconCache.invalidateAll();
faviconCache.clear();
loadLocalFavicons();
}
@@ -113,7 +106,7 @@ public class FaviconHandler<F>{
logger.debug(FaviconHandler.class, "Resolving image file '%s'...", input);
return CompletableFuture.completedFuture(localFavicons.get(input.toLowerCase(Locale.ROOT)));
}else{
logger.debug(FaviconHandler.class, "Resolving Name/UUID as https://mc-heads.net/avatar/%s/64...", input);
logger.debug(FaviconHandler.class, "Resolving '%s' as 'https://mc-heads.net/avatar/%s/64'...", input, input);
return CompletableFuture.supplyAsync(() -> fromURL(core, "https://mc-heads.net/avatar/" + input + "/64"), this.faviconThreadPool);
}
}
@@ -137,15 +130,15 @@ public class FaviconHandler<F>{
private void loadLocalFavicons(){
this.localFavicons.clear();
logger.info("Loading local image files as Favicons...");
logger.info("[-] Loading local image files as Favicons...");
Path folder = core.getPlugin().getFolderPath().resolve("favicons");
if(!Files.exists(folder)){
try{
Files.createDirectories(folder);
logger.info("Created favicons folder.");
logger.debugSuccess(FaviconHandler.class, "Created favicons folder.");
}catch(IOException ex){
logger.warn("Cannot create favicons folder. Encountered an IOException.", ex);
logger.failure("Cannot create favicons folder. Encountered an IOException.", ex);
return;
}
}
@@ -154,8 +147,10 @@ public class FaviconHandler<F>{
pathStream.filter(Files::isRegularFile)
.filter(file -> file.getFileName().toString().endsWith(".png"))
.forEach(this::loadFile);
logger.success("Loaded <white>%d</white> local Favicons.", localFavicons.size());
}catch(IOException ex){
logger.warn("Cannot load files from Favicons folder.", ex);
logger.failure("Cannot load files from favicons folder.", ex);
}
}
@@ -163,14 +158,14 @@ public class FaviconHandler<F>{
try(InputStream stream = Files.newInputStream(path)){
F favicon = createFavicon(stream);
if(favicon == null){
logger.warn("Cannot create Favicon from file '%s'. Received Favicon was null.", path.getFileName().toString());
logger.failure("Cannot create Favicon from file '<white>%s</white>'. Received Favicon was null.", path.getFileName().toString());
return;
}
localFavicons.put(path.getFileName().toString().toLowerCase(Locale.ROOT), favicon);
logger.info("Loaded file '%s' as Favicon.", path.getFileName().toString());
logger.debugSuccess(FaviconHandler.class, "Loaded file '<white>%s</white>' as Favicon.", path.getFileName().toString());
}catch(IOException ex){
logger.warn("Cannot create Favicon from file '%s'. Encountered IOException.", ex);
logger.failure("Cannot create Favicon from file '<white>%s</white>'. Encountered IOException.", ex);
}
}
@@ -185,11 +180,11 @@ public class FaviconHandler<F>{
try(InputStream stream = client.send(request, HttpResponse.BodyHandlers.ofInputStream()).body()){
return createFavicon(stream);
}catch(InterruptedException | IOException ex){
logger.warn("Cannot create Favicon from URL '%s'. Encountered an Exception.", ex, url);
logger.warn("Cannot create Favicon from URL '<white>%s</white>'. Encountered an Exception.", ex, url);
return null;
}
}catch(URISyntaxException ex){
logger.warn("Cannot create Favicon from URL '%s'. Encountered an IOException.", ex, url);
logger.warn("Cannot create Favicon from URL '<white>%s</white>'. Encountered a URISyntaxException.", ex, url);
return null;
}
}
@@ -79,40 +79,40 @@ public class PlayerHandler{
try(BufferedReader reader = Files.newBufferedReader(cache)){
cachedPlayers = gson.fromJson(reader, listType);
}catch(IOException ex){
logger.warn("Encountered IOException while reading the playercache.json file!", ex);
logger.warn("Encountered IOException while reading the <white>playercache.json</white> file!", ex);
return;
}catch(JsonIOException | JsonSyntaxException ex){
logger.warn("Encountered JsonSyntaxException while parsing playercache.json file!", ex);
logger.warn("Encountered JsonSyntaxException while parsing <white>playercache.json</white> file!", ex);
return;
}
// In case Gson messes up... I guess.
if(cachedPlayers == null){
logger.warn("Couldn't load players from playercache.json file. Is the JSON valid?");
logger.warn("Couldn't load players from <white>playercache.json</white> file. Is the JSON valid?");
// Create new ArrayList instance to avoid further issues.
cachedPlayers = new ArrayList<>();
return;
}
logger.info("Loaded %d players into cache!", cachedPlayers.size());
logger.success("Loaded <white>%d</white> players into cache!", cachedPlayers.size());
}
public void save(){
if(cachedPlayers.isEmpty()){
logger.info("No players in cache to save. Skipping...");
logger.debug(PlayerHandler.class, "No players in cache to save. Skipping...");
return;
}
if(core.getFileHandler().getBoolean("disableCache")){
logger.info("'disable_cache' is set to true. Skipping saving of cached players...");
logger.debug(PlayerHandler.class, "[-] 'disable_cache' is set to true. Not saving cached players.");
return;
}
try(BufferedWriter writer = Files.newBufferedWriter(cache, StandardCharsets.UTF_8)){
gson.toJson(cachedPlayers, listType, writer);
logger.info("Successfully saved playercache.json file.");
logger.success("Successfully saved <white>playercache.json</white> file.");
}catch(IOException ex){
logger.warn("Encountered IOException while saving cached players to playercache.json!", ex);
logger.failure("Encountered IOException while saving cached players to <white>playercache.json</white>!", ex);
}
}
@@ -56,6 +56,7 @@ public class ProfileManager{
NullBool extraPlayersEnabled = resolve(defEntry, entry, ProfileEntry::extraPlayersEnabled);
NullBool maxPlayersEnabled = resolve(defEntry, entry, ProfileEntry::maxPlayersEnabled);
NullBool onlinePlayersEnabled = resolve(defEntry, entry, ProfileEntry::onlinePlayersEnabled);
NullBool hidePlayersHoverEnabled = resolve(defEntry, entry, ProfileEntry::hidePlayersHoverEnabled);
String extraPlayersCount = resolve(defEntry, entry, ProfileEntry::extraPlayersCount);
String maxPlayersCount = resolve(defEntry, entry, ProfileEntry::maxPlayersCount);
String onlinePlayersCount = resolve(defEntry, entry, ProfileEntry::onlinePlayersCount);
@@ -69,6 +70,7 @@ public class ProfileManager{
.extraPlayersEnabled(extraPlayersEnabled)
.maxPlayersEnabled(maxPlayersEnabled)
.onlinePlayersEnabled(onlinePlayersEnabled)
.hidePlayersHoverEnabled(hidePlayersHoverEnabled)
.extraPlayersCount(extraPlayersCount)
.maxPlayersCount(maxPlayersCount)
.onlinePlayersCount(onlinePlayersCount)
@@ -96,6 +98,7 @@ public class ProfileManager{
NullBool extraPlayersEnabled = resolveNullBool(node, "playerCount", "extraPlayers", "enabled");
NullBool maxPlayersEnabled = resolveNullBool(node, "playerCount", "maxPlayers", "enabled");
NullBool onlinePlayersEnabled = resolveNullBool(node, "playerCount", "onlinePlayers", "enabled");
NullBool hidePlayersHoverEnabled = resolveNullBool(node, "playerCount", "hidePlayersHover");
String extraPlayers = resolveNullableString(node, "playerCount", "extraPlayers", "amount");
String maxPlayers = resolveNullableString(node, "playerCount", "maxPlayers", "amount");
String onlinePlayers = resolveNullableString(node, "playerCount", "onlinePlayers", "amount");
@@ -109,6 +112,7 @@ public class ProfileManager{
.extraPlayersEnabled(extraPlayersEnabled)
.maxPlayersEnabled(maxPlayersEnabled)
.onlinePlayersEnabled(onlinePlayersEnabled)
.hidePlayersHoverEnabled(hidePlayersHoverEnabled)
.extraPlayersCount(extraPlayers)
.maxPlayersCount(maxPlayers)
.onlinePlayersCount(onlinePlayers)
@@ -53,6 +53,7 @@ public class ProfileSerializer implements TypeSerializer<ProfileEntry>{
node.node("playerCount", "text").set(profile.playerCountText());
node.node("favicon").set(profile.favicon());
node.node("playerCount", "hidePlayers").set(profile.hidePlayersEnabled().getOrDefault(false));
node.node("playerCount", "hidePlayersHover").set(profile.hidePlayersHoverEnabled().getOrDefault(false));
node.node("playerCount", "extraPlayers", "enabled").set(profile.extraPlayersEnabled().getOrDefault(false));
node.node("playerCount", "extraPlayers", "amount").set((profile.extraPlayersCount() == null) ? "0" : profile.extraPlayersCount());
node.node("playerCount", "maxPlayers", "enabled").set(profile.maxPlayersEnabled().getOrDefault(false));
@@ -111,7 +111,7 @@ public class StringReplacer{
raw.append(' ').append(valuesStr);
if(collector != null)
collector.appendWarningFormatted(index, "Placeholder '%s' does not have a closing bracket (})", raw.toString());
collector.appendWarningFormatted(index, "Placeholder '<white>%s</white>' does not have a closing bracket (})", raw.toString());
return raw.toString();
}
@@ -126,7 +126,7 @@ public class StringReplacer{
raw.append('}');
if(collector != null)
collector.appendWarningFormatted(placeholderStart, "Placeholder '%s' does not have any available PlaceholderProvider", raw.toString());
collector.appendWarningFormatted(placeholderStart, "Placeholder '<white>%s</white>' does not have any available PlaceholderProvider", raw.toString());
return raw.toString();
}
@@ -141,7 +141,7 @@ public class StringReplacer{
raw.append('}');
if(collector != null)
collector.appendWarningFormatted(valueStart, "Placeholder '%s' has an invalid value String '%s'", raw.toString(), valuesStr);
collector.appendWarningFormatted(valueStart, "Placeholder '<white>%s</white>' has an invalid value String '<white>%s</white>'", raw.toString(), valuesStr);
return raw.toString();
}
+14
View File
@@ -48,6 +48,14 @@ checkUpdates: true
#
debug: false
#
# Should the "ASL" ASCI art be displayed on plugin startup?
# Setting this to false will hide the banner.
#
# Default: true
#
printBanner: true
#
# Sets how long Favicons should be cached. Value is in minutes with the lowest possible value being 1.
#
@@ -84,6 +92,12 @@ disableDuringMaintenance:
#
hidePlayers: true
#
# Whether hiding player list on hover should be disabled while Maintenance mode is active.
#
# Default: true
#
hidePlayersHover: true
#
# Whether player count text changing should be disabled while Maintenance mode is active.
#
# Default: true
@@ -95,6 +95,15 @@ playerCount:
#
hidePlayers: false
#
# Set whether the List of players is shown when hovering over the player count.
# Setting this to true will also make the plugin ignore the hover option below.
#
# If not present, defaults to false.
#
# Read more: https://asl.andre601.ch/profiles/#hideplayershover
#
hidePlayersHover: false
#
# Set the lines shown when the player hovers over the player count.
# Unlike the MOTD option does this one only support basic color and formatting codes. Any RGB colors will be
# downsampled to the nearest matching color.
@@ -23,7 +23,7 @@
*
*/
package ch.andre601.advancedserverlist.test;
package ch.andre601.advancedserverlist.core;
import ch.andre601.advancedserverlist.core.interfaces.PluginLogger;
@@ -23,7 +23,7 @@
*
*/
package ch.andre601.advancedserverlist.test;
package ch.andre601.advancedserverlist.core;
import ch.andre601.advancedserverlist.core.profiles.conditions.ProfileConditionParser;
import ch.andre601.expressionparser.ParseWarnCollector;
@@ -23,7 +23,7 @@
*
*/
package ch.andre601.advancedserverlist.test;
package ch.andre601.advancedserverlist.core;
import com.unascribed.flexver.FlexVerComparator;
import org.junit.jupiter.api.Test;
+1 -1
View File
@@ -17,7 +17,7 @@
import re
from mkdocs.config.defaults import MkDocsConfig
from mkdocs.structure.files import File, Files
from mkdocs.structure.files import Files
from mkdocs.structure.pages import Page
def on_page_markdown(
+1 -1
View File
@@ -2,6 +2,6 @@
{% block announce %}
<span class="twemoji">{% include ".icons/material/party-popper.svg" %}</span>
Celebrating <a href="https://andre601.ch/blog/2024/12-13-history-of-asl/" target="_blank" rel="nofollow">25,000 downloads!</a> Thank you!
Celebrating <a href="https://andre601.ch/blog/2024/12-13-history-of-asl/" target="_blank" rel="nofollow"><b>25,000 downloads!</b></a> Thank you!
<span class="twemoji">{% include ".icons/material/party-popper.svg" %}</span>
{% endblock %}
+183
View File
@@ -0,0 +1,183 @@
---
icon: octicons/plus-circle-24
---
AdvancedServerList allows you to add your own Placeholders to be useable through its `${identifier value}` format.
The benefit of this method over alternatives such as using PlaceholderAPI placeholders is availability and the fact that these placeholders can be used within the [`condition` option](../profiles/index.md#condition) of the plugin.
Adding your own placeholder is relatively easy and only requires the main API module to be used in your dependencies, unless you need platform-specific values (Explained later on).
## Create your PlaceholderProvider class
AdvancedServerList's placeholder system uses a dedicated [`PlaceholderProvider`](reference/api/ch.andre601.advancedserverlist.api/placeholderprovider.md) class where the placeholder gets defined and its values parsed.
In order to add your own, you'll need to first create a new class and make it extend the `PlaceholderProvider` class:
```java title="MyPlaceholder.java"
public class MyPlaceholder extends PlaceholderProvider {
}
```
Your IDE should now report issues, namely a missing method to override and a missing Constructor to match the extended class' one. Your IDE should allow you to easily implement those, if not, then manually add the constructor with a `String` argument and the `public String parsePlaceholder` method with `String`, [`GenericPlayer`](reference/api/ch.andre601.advancedserverlist.api/objects/genericplayer.md) and [`GenericServer`](reference/api/ch.andre601.advancedserverlist.api/objects/genericserver.md) arguments:
```java { title="MyPlaceholder.java" .annotate }
public class MyPlaceholder extends PlaceholderProvider {
public MyPlaceholder(String identifier) {
super(identifier); // (1)
}
@Override
public String parsePlaceholder(String placeholder, GenericPlayer player, GenericServer server) {
return null;
}
}
```
1. It is recommended to replace the Constructor with a No-Args Constructor and set the placeholder name directly in the `super` call:
```java
public MyPlaceholder() {
super("myplaceholder");
}
```
## Parsing placeholders
To now parse your placeholders, all you need to do is edit the `parsePlaceholder` method.
AdvancedServerList calls this method whenever it finds a matching identifier of a PlaceholderProvider, with the `String` argument being the actual value after the identifier and the `GenericPlayer` and `GenericServer` arguments holding the Player and server values respectively.
As an example, assuming you simply want to return `Hello` for the placeholder `${myplaceholder sayHello}`, this would be the code for it:
```java { title="MyPlaceholder.java" .annotate }
public class MyPlaceholder extends PlaceholderProvider {
public MyPlaceholder() {
super("myplaceholder");
}
@Override
public String parsePlaceholder(String placeholder, GenericPlayer player, GenericServer server) {
if (placeholder.equals("sayHello")) {
return "Hello";
}
return null; // (1)
}
}
```
1. Returning `null` tells AdvancedServerList that the placeholder was invalid, making it return the placeholder unchanged, while also reporting an error, if used in `condition`.
## Registering the Placeholder
The final step now would be to register your placeholder for AdvancedServerList to use.
To do this, obtain an instance of the [`AdvancedServerListAPI`](reference/api/ch.andre601.advancedserverlist.api/advancedserverlistapi.md) through its static `get()` method and then call `addPlaceholderProvider(PlaceholderProvider)` through it:
/// tab | :fontawesome-solid-paper-plane: Paper Example
```java title="MyPlugin.java"
public class MyPlugin extends JavaPlugin {
@Override
public void onEnable() {
AdvancedServerListAPI api = AdvancedServerListAPI.get();
api.addPlaceholderProvider(new MyPlaceholder());
getLogger().info("Hooked into AdvancedServerList!");
}
}
```
///
/// tab | :octicons-git-merge-24: BungeeCord Example
```java title="MyPlugin.java"
public class MyPlugin extends Plugin {
@Override
public void onEnable() {
AdvancedServerListAPI api = AdvancedServerListAPI.get();
api.addPlaceholderProvider(new MyPlaceholder());
getLogger().info("Hooked into AdvancedServerList!");
}
}
```
///
/// tab | :fontawesome-solid-paper-plane: Velocity Example
```java title="MyPlugin.java"
@Plugin(
id = "myplugin",
version = "1.0.0",
authors = {"You?"},
dependencies = {
@Dependency(id = "advancedserverlist")
}
)
public class MyPlugin {
private final ProxyServer proxy;
private final Logger logger;
@Inject
public MyPlugin(ProxyServer proxy, Logger logger) {
this.proxy = proxy;
this.logger = logger;
}
@Subscribe
public void onProxyInitialization(ProxyInitializeEvent event) {
AdvancedServerListAPI api = AdvancedServerListAPI.get();
api.addPlaceholderProvider(new MyPlaceholder());
logger.info("Hooked into AdvancedServerList!");
}
}
```
///
## You're done
Your Placeholders should now be registered successfully if they check the following:
- The provided identifier is not `null` nor empty.
- The provided identifier does not contain spaces.
- The provided identifier is not already registered in the API.
## Note on `GenericPlayer` and `GenericServer`
The different platform APIs of AdvancedServerList provide extended versions of the `GenericPlayer` and `GenericServer` instances used, providing additional methods otherwise not available.
Should you want to use these specific instances will you need to add the platform API as an additional dependency to your project and then cast the `GenericPlayer` and/or `GenericServer` instance to the platform specific version.
Below is an example using the `platform-bukkit` API (This assumes your plugin is one running on Paper):
```java { title="MyPlaceholder.java" .annotate }
public class MyPlaceholder extends PlaceholderProvider {
public MyPlaceholder() {
super("myplaceholder");
}
@Override
public String parsePlaceholder(String placeholder, GenericPlayer player, GenericServer server) {
if (!(player instanceof BukkitPlayer bukkitPlayer)) {
return null; // (1)
}
return switch (placeholder) {
case "playedBefore" -> String.valueOf(bukkitPlayer.hasPlayedBefore());
case "whitelisted" -> String.valueOf(bukkitPlayer.isWhitelisted());
case "banned" -> String.valueOf(bukkitPlayer.isBanned());
default -> null;
};
}
}
```
1. Depending on the Java version you use will you need to alter the code:
```java
if (!(player instanceof BukkitPlayer)) {
return null;
}
// You need to cast it separately.
BukkitPlayer bukkitPlayer = (BukkitPlayer) player;
// ...use bukkitPlayer here.
```
+15 -9
View File
@@ -7,6 +7,15 @@ icon: octicons/log-24
This page lists the recent changes made towards the AdvancedServerListAPI.
They are ordered newest to oldest.
## v4.1.0 { #v4.1.0 }
### :octicons-plus-circle-24:{ .changelog-added title="Added" } Added { #v-added }
- New setter method added to [`ProfileEntry.Builder`][profileentry.builder] class:
- [`hidePlayersHoverEnabled(NullBool)`][hidePlayersHoverEnabled]
[hidePlayersHoverEnabled]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#hideplayershoverenabled(nullbool)
## v4.0.0 { #v4.0.0 }
### :octicons-alert-24:{ .changelog-breaking title="Breaking Changes" } Breaking Changes { #v4.0.0-breaking }
@@ -21,7 +30,7 @@ They are ordered newest to oldest.
- [`onlinePlayersCount(String)`][onlinePlayersCount]
- [`onlinePlayersEnabled(NullBool)`][onlinePlayersEnabled]
[onlinePlayersCount]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#onlineplayerscount(integer)
[onlinePlayersCount]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#onlineplayerscount(string)
[onlinePlayersEnabled]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#onlineplayersenabled(nullbool)
## v3.3.0 { #v3.3.0 }
@@ -53,11 +62,11 @@ They are ordered newest to oldest.
- [`playerCountText(String)`][playercounttext]
- [`players(List<String>)`][players]
[extraplayerscount]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#extraplayerscount(integer)
[extraplayerscount]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#extraplayerscount(string)
[extraplayersenabled]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#extraplayersenabled(nullbool)
[favicon]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#favicon(string)
[hideplayersenabled]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#hideplayersenabled(nullbool)
[maxplayerscount]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#maxplayerscount(integer)
[maxplayerscount]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#maxplayerscount(string)
[maxplayersenabled]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#maxplayersenabled(nullbool)
[motd]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#motd(list<string>)
[playercounttext]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#playercounttext(string)
@@ -119,9 +128,9 @@ They are ordered newest to oldest.
- [`maxPlayersEnabled()`][maxplayersenabled] - Returns the value for `playerCount -> maxPlayers -> enabled` as `NullBool`
- [`maxPlayersCount()`][maxplayerscount] - Returns the value of `playerCount -> maxPlayers -> amount` as nullable `Integer`
- New setters in [`ProfileEntry.Builder`][profileentry.builder]:
- [`setMaxPlayersEnabled(NullBool)`][setmaxplayersenabled] - Sets whether the max players feature is enabled or not.
- [`setMaxPlayersCount(Integer)`][setmaxplayerscount] - Sets the number to use for the max players count.
- [`setExtraPlayersCount(Integer)`][setextraplayersecount] - Replacement for [deprecated `setExtraPlayerCount(Integer)` method](#v3.1.0-deprecated).
- `setMaxPlayersEnabled(NullBool)`- Sets whether the max players feature is enabled or not.
- `setMaxPlayersCount(Integer)` - Sets the number to use for the max players count.
- `setExtraPlayersCount(Integer)` - Replacement for [deprecated `setExtraPlayerCount(Integer)` method](#v3.1.0-deprecated).
### :octicons-zap-24:{ .changelog-breaking title="Deprecated" } Deprecated { #v3.1.0-deprecated }
@@ -132,9 +141,6 @@ They are ordered newest to oldest.
[maxplayerscount]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/index.md#maxplayerscount()
[profileentry.builder]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md
[setmaxplayersenabled]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#setmaxplayersenabled(nullbool)
[setmaxplayerscount]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#setmaxplayerscount(integer)
[setextraplayersecount]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#setextraplayerscount(integer)
----
+7 -111
View File
@@ -84,127 +84,23 @@ dependencies {
```
///
## Adding your own Placeholders
## Tutorials
<!-- admo:info This requires the main api module -->
Dedicated pages to explain aspects of the API are available:
The API allows you to add your own placeholders which would be available through the `${<identifier> <values>}` placeholder Pattern in a Server List profile.
<div class="grid cards" markdown>
To add your own placeholders, follow these steps:
- [:octicons-plus-circle-24: Adding Placeholders](adding-placeholders.md)
- [:octicons-broadcast-24: Listening for Events](listening-for-events.md)
### 1. Create a Placeholder class
</div>
You should first create a new class and make it extend the abstract [`PlaceholderProvider`][placeholderprovider]. This class includes a constructor and a method you need to add, so do that:
```java { .annotate title="Creating PlaceholderProvider class" }
public class MyPlaceholders extends PlaceholderProvider {
public MyPlaceholders(String identifier) {
super(identifier); // (1)
}
@Override
public String parsePlaceholder(String placeholder, GenericPlayer player, GenericServer server) {
return null;
}
}
```
1. It is recommended to replace this constructor with a no-args Constructor and have the placeholder name set in the `super` directly:
```java
public MyPlaceholders() {
super("myplaceholders");
}
```
The [`parsePlaceholder(String, GenericPlayer, GenericServer)`][parseplaceholder] method is used by AdvancedServerList to replace a matching placeholder with a value.
What you return is completely up to you. Just keep in mind that returning `null` will be treated as an invalid placeholder by AdvancedServerList, resulting in the placeholder being returned as-is without any changes.
[placeholderprovider]: reference/api/ch.andre601.advancedserverlist.api/placeholderprovider.md
[parseplaceholder]: reference/api/ch.andre601.advancedserverlist.api/placeholderprovider.md#parseplaceholder(string,-genericplayer,-genericserver)
/// details | Example of final class
type: example
```java title="MyPlaceholders.java"
public class MyPlaceholders extends PlaceholderProvider {
public MyPlaceholders() {
super("myplaceholders");
}
@Override
public String parsePlaceholder(String placeholder, GenericPlayer player, GenericServer server) {
if(placeholder.equalsIgnoreCase("hello"))
return "Hello " + player.getName();
return null;
}
}
```
///
### 2. Register the Placeholders
Next step should be to register the placeholder. To achieve this, first obtain an instance of the [`AdvancedServerListAPI`][advancedserverlistapi] by using the static [`get()`][get] method. After that call [`addPlaceholderProvider(PlaceholderProvider placeholderProvider)`][addplaceholderprovider].
Your code may look similar to this:
```java title="Registering PlaceholderProvider class"
public class MyPlugin extends JavaPlugin {
@Override
public void onEnable() {
AdvancedServerListAPI api = AdvancedServerListAPI.get();
api.addPlaceholderProvider(new MyPlaceholders());
}
}
```
[advancedserverlistapi]: reference/api/ch.andre601.advancedserverlist.api/advancedserverlistapi.md
[get]: reference/api/ch.andre601.advancedserverlist.api/advancedserverlistapi.md#get()
[addplaceholderprovider]: reference/api/ch.andre601.advancedserverlist.api/advancedserverlistapi.md#addplaceholderprovider(placeholderprovider)
This should register your PlaceholderExpansion as long as it is valid, meaning that the identifier...
- ...is not `null`
- ...is not containing spaces
- ...is not using a name already registered in the API
### 3. Declare AdvancedServerList as (soft)depend
## Declare AdvancedServerList as (soft)depend
The final thing you should make sure is to define AdvancedServerList as a depend or soft-depend for your plugin, to make sure it loads after AdvancedServerList.
Below are example setups for Spigot, Paper, BungeeCord and Velocity:
/// tab | :simple-spigotmc: Spigot
//// tab | Softdepend
```yaml title="plugin.yml"
name: "MyPlugin"
author: "author"
version: "1.0.0"
main: "com.example.plugin.ExamplePlugin"
softdepend:
- AdvancedServerList
```
////
//// tab | Depend
```yaml title="plugin.yml"
name: "MyPlugin"
author: "author"
version: "1.0.0"
main: "com.example.plugin.ExamplePlugin"
depend:
- AdvancedServerList
```
////
///
/// tab | :fontawesome-solid-paper-plane: Paper
//// tab | Softdepend
```yaml title="paper-plugin.yml"
+65
View File
@@ -0,0 +1,65 @@
---
icon: octicons/broadcast-24
---
AdvancedServerList provides some events that your plugin can listen to.
These events are structurally the same across all platforms, yet still require the usage of Platform-specific APIs due to how some events are registered and handled on some platforms.
The plugin currently offers the following events:
<div class="grid cards" markdown>
- [PreServerListSetEvent](#preserverlistsetevent)
- [PostServerListSetEvent](#postserverlistsetevent)
</div>
## PreServerListSetEvent
This event is executed before AdvancedServerList tries to modify the player list.
### Methods { #preserverlistsetevent-methods }
The following methods are available to use:
| Method | Description |
|--------------------------|--------------------------------------------------------------------------------|
| `getEntry()` | Returns the [`ProfileEntry`][profileentry] that AdvancedServerList should use. |
| `setEntry(ProfileEntry)` | Sets the [`ProfileEntry`][profileentry] that should be used. |
| `isCancelled()` | Returns wether this event was cancelled or not. |
| `setCancelled(boolean)` | Sets the event's cancelled state. |
### Notes { #preserverlistsetevent-notes }
- Providing `null` as the `ProfileEntry` for `setEntry(ProfileEntry)` is not allowed. Also, a copy of the provided `ProfileEntry` is made and used.
## PostServerListSetEvent
This event is executed after AdvancedServerList completed the server list modifications.
### Methods { #postserverlistsetevent-methods }
| Method | Description |
|--------------------------|--------------------------------------------------------------------------|
| `getEntry()` | Returns the [`ProfileEntry`][profileentry] that AdvancedServerList used. |
### Notes { #postserverlistsetevent-notes }
- The returned `ProfileEntry` is not an actual representation of what is being displayed to the player and rather an instance of values the plugin received.
The returned value may also be `null` should the Ping Event be cancelled due to things such as an invalid Protocol or the [`PreServerListSetEvent`](#preserverlistsetevent) being cancelled.
## Registering the Events
To register an event, please refer to our platforms documentation on how to do this.
## What is a ProfileEntry?
Both events provide and use a so called [`ProfileEntry`][profileentry].
The `ProfileEntry` is a record, representing the values of a [Server List Profile](../profiles/index.md), meaning all values you can set in the profile configuration (Except condition and priority) will be available as a `ProfileEntry`.
It is important to note, that the `ProfileEntry` can be a representation of the [`profiles` option](../profiles/index.md#profiles), the global options or a mixture of both.
If you want to create your own instance, you'll need to use the [`ProfileEntry.Builder` class](reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md) as the `ProfileEntry` is unmodifiable.
A convenience method in the form of `builder()` exists to create a Builder instance of the ProfileEntry with its values already pre-set.
[profileentry]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/index.md
@@ -13,14 +13,16 @@ constructors:
methods:
- name: 'extraPlayersCount'
description: |
Sets the number of players to add to the online players to use as the new max players value.<br>
Sets the value to resolve into a number by AdvancedServerList.<br>
Strings not resolving into valid numbers will be treated as this option not being set.<br>
<br>
This option has no effect when <a href="../#extraplayersenabled()"><code>extraPlayersEnabled</code></a> is set to <a href="../../../objects/nullbool/#false"><code>NullBool.FALSE</code></a> or <a href="../../objects/nullbool/#not_set"><code>NullBool.NOT_SET</code></a>.<br>
<br>
Set this to <code>null</code> to not alter the max player count. Alternatively, set <a href="#setextraplayersenabled(nullbool)"><code>setExtraPlayersEnabled(NullBool)</code></a> to <a href="../../../objects/nullbool/#false"><code>NullBool.FALSE</code></a>.
Set this to <code>null</code> to not alter the max player count. Alternatively <a href="#setextraplayersenabled(nullbool)"><code>disable extra Players</code></a>.
parameters:
- name: 'extraPlayersCount'
description: 'The number of extra players to add.'
type: 'Integer'
type: 'String'
attribute:
- 'nullable'
returns: 'This Builder after the extra player count has been set. Useful for chaining.'
@@ -71,9 +73,26 @@ methods:
throws:
- name: 'IllegalArgumentException'
description: 'Thrown by the <code>CheckUtil</code> in case <code>null</code> has been provided as parameter.'
- name: 'hidePlayersHoverEnabled'
description: |
Sets ehther the Hover List of online Players should be hidden or not.<br>
<br>
Set to <a href="../../../objects/nullbool/#not_set"><code>NullBool.NOT_SET</code></a> to not set this.
parameters:
- name: 'hidePlayersHoverEnabled'
description: 'Whether the online players feature should be enabled or not.'
type: 'NullBool'
attribute:
- 'notnull'
returns: 'This Builder after the NullBool has been set. Useful for chaining.'
throws:
- name: 'IllegalArgumentException'
description: 'Thrown by the <code>CheckUtil</code> in case <code>null</code> has been provided as parameter.'
- name: 'maxPlayersCount'
description: |
Sets the number of max players allowed to join this server.<br>
Sets the value to resolve into a number by AdvancedServerList.<br>
Strings not resolving into valid numbers will be treated as this option not being set.<br>
<br>
This option has no effect when <a href="#maxplayersenabled(nullbool)">maxPlayersEnabled</a> is set to <a href="../../../objects/nullbool/#false"><code>NullBool.FALSE</code></a> or <a href="../../../objects/nullbool/#not_set"><code>NullBool.NOT_SET</code></a>.<br>
<br>
Set this to <code>null</code> to not alter the max player count. Alternatively <a href="#maxplayersenabled(nullbool)">disable max Players</a>.
@@ -122,7 +141,11 @@ methods:
- name: 'onlinePlayersCount'
description: |
Sets the value to resolve into a number by AdvancedServerList.<br>
Strings not resolving into valid numbers will be treated as this option not being set.
Strings not resolving into valid numbers will be treated as this option not being set.<br>
<br>
This option has no effect when <a href="#onlineplayersenabled(nullbool)">maxPlayersEnabled</a> is set to <a href="../../../objects/nullbool/#false"><code>NullBool.FALSE</code></a> or <a href="../../../objects/nullbool/#not_set"><code>NullBool.NOT_SET</code></a>.<br>
<br>
Set this to <code>null</code> to not alter the online player count. Alternatively <a href="#onlineplayersenabled(nullbool)">disable online Players</a>.
parameters:
- name: 'onlinePlayersCount'
description: 'The number of online players to set.'
@@ -42,14 +42,24 @@ constructors:
type: NullBool
attributes:
- notnull
- name: 'onlinePlayersCount'
description: 'The number to set for the Online Players count.'
type: String
attributes:
- nullable
- name: 'hidePlayersHoverEnabled'
description: 'Whether the online Players Hover should beenabled.'
type: NullBool
attributes:
- notnull
- name: 'extraPlayersCount'
description: 'The number to add to the online players for the max players number.'
type: Integer
description: 'The number to add to the online Players for the extra Players.'
type: String
attributes:
- nullable
- name: 'maxPlayersCount'
description: 'The number to use for the max players count.'
type: Integer
description: 'The number to set for the max Players count.'
type: String
attributes:
- nullable
seealso:
@@ -74,8 +84,11 @@ methods:
<li><a href="#hideplayersenabled()"><code>hidePlayersEnabled</code></a>: <a href="../../objects/nullbool/#not_set"><code>NullBool.NOT_SET</code></a></li>
<li><a href="#extraplayersenabled()"><code>extraPlayersEnabled</code></a>: <a href="../../objects/nullbool/#not_set"><code>NullBool.NOT_SET</code></a></li>
<li><a href="#maxplayersenabled()"><code>maxPlayersEnabled</code></a>: <a href="../../objects/nullbool/#not_set"><code>NullBoo.NOT_SET</code></a></li>
<li><a href="#onlineplayersenabled()"><code>onlinePlayersEnabled</code></a>: <a href="../../objects/nullbool/#not_set"><code>NullBoo.NOT_SET</code></a></li>
<li><a href="#hideplayershoverenabled()"><code>hidePlayersHoverEnabled</code></a>: <a href="../../objects/nullbool/#not_set"><code>NullBoo.NOT_SET</code></a></li>
<li><a href="#extraplayerscount()"><code>extraPlayersCount</code></a>: <code>null</code></li>
<li><a href="#maxplayerscount()"><code>maxPlayersCount</code></a>: <code>null</code></li>
<li><a href="#onlineplayerscount()"><code>onlinePlayersCount</code></a>: <code>null</code></li>
</ul>
returns: 'New ProfileEntry instance with empty/null values defined'
attributes:
@@ -139,7 +152,7 @@ methods:
- name: 'hidePlayersEnabled'
description: |
Whether the player count should be hidden or not in this ProfileEntry.<br>
To get the actual boolean value, append <a href="../../objects/nullbool/#getordefault()"><code>getOrDefault(boolean)</code></a>.
To get the actual boolean value, append <a href="../../objects/nullbool/#getordefault(boolean)"><code>getOrDefault(boolean)</code></a>.
returns: 'Whether the player count should be hidden or not in this ProfileEntry.'
type:
name: 'NullBool'
@@ -148,7 +161,7 @@ methods:
- name: 'extraPlayersEnabled'
description: |
Whether the extra players feature should be used or not in this ProfileEntry.<br>
To get the actual boolean value, append <a href="../../objects/nullbool/#getordefault()"><code>getOrDefault(boolean)</code></a>.
To get the actual boolean value, append <a href="../../objects/nullbool/#getordefault(boolean)"><code>getOrDefault(boolean)</code></a>.
returns: 'Whether the extra players feature should be used or not in this ProfileEntry.'
type:
name: 'NullBool'
@@ -157,19 +170,39 @@ methods:
- name: 'maxPlayersEnabled'
description: |
Whether the max players feature should be used or not.<br>
To get the actual boolean value, append <a href="../../objects/nullbool/#getordefault()"><code>getOrDefault(boolean)</code></a>.
To get the actual boolean value, append <a href="../../objects/nullbool/#getordefault(boolean)"><code>getOrDefault(boolean)</code></a>.
returns: 'Whether the max players feature should be used or not.'
type:
name: 'NullBool'
type: 'object'
link: '../../objects/nullbool/'
- name: 'onlinePlayersEnabled'
description: |
Whether the online players feature should be used or not.<br>
To get the actual boolean value, append <a href="../../objects/nullbool/#getordefault(boolean)"><code>getOrDefault(boolean)</code></a>.
returns: 'Whether the online players feature should be used or not.'
type:
name: 'NullBool'
type: 'object'
link: '../../objects/nullbool/'
- name: 'hidePlayersHoverEnabled'
description: |
Whether the Hover List of online players should be hidden or not.<br>
To get the actual boolean value, use <a href="../../objects/nullbool/#getordefault()"><code>getOrDefault(boolean)</code></a> of the returned <a href="../../objects/nullbool"><code>NullBool</code></a>.<br>
<br>
When true, this option will force the <a href="#players()"><code>players()</code></a> list to be ignored.
returns: 'Whether the Hover List of online players should be hidden or not.'
type:
name: 'NullBool'
type: 'object'
link: '../../objects/nullbool/'
- name: 'extraPlayersCount'
description: 'Gets the currently set number of extra players to use by this ProfileEntry. May be <code>null</code>.'
returns: 'Possibly-null integer number of extra players used by this ProfileEntry.'
description: 'Gets the currently set number of extra players of this ProfileEntry.'
returns: 'The current number of extra players used by this ProfileEntry.'
attributes:
- nullable
type:
name: 'Integer'
name: 'String'
type: 'object'
- name: 'maxPlayersCount'
description: 'Gets the currently set number of max players of this ProfileEntry.'
@@ -177,7 +210,15 @@ methods:
attributes:
- nullable
type:
name: 'Integer'
name: 'String'
type: 'object'
- name: 'onlinePlayersCount'
description: 'Gets the currently set number of online players of this ProfileEntry.'
returns: 'The current number of online players used by this ProfileEntry.'
attributes:
- nullable
type:
name: 'String'
type: 'object'
- name: 'isInvalid'
description: |
+4
View File
@@ -51,4 +51,8 @@
text-overflow: ellipsis;
white-space: nowrap;
display: block;
}
.md-banner__inner.md-grid.md-typeset {
text-align: center;
}
+30 -4
View File
@@ -41,10 +41,7 @@ AdvancedServerList is available on the following places (Please note the current
</div>
[modrinth]: https://modrinth.com/plugin/advancedserverlist
[codeberg]: https://codeberg.org/Andre601/AdvancedServerList
[spigot]: https://www.spigotmc.org/resources/102910/
[hangar]: https://hangar.papermc.io/Andre_601/AdvancedServerList
The plugin is available for Paper, BungeeCord, Waterfall and Velocity, and has been tested on these platforms. It may support additional forks, but this is not guaranteed.
@@ -247,6 +244,15 @@ playerCount:
#
hidePlayers: false
#
# Set whether the List of players is shown when hovering over the player count.
# Setting this to true will also make the plugin ignore the hover option below.
#
# If not present, defaults to false.
#
# Read more: https://asl.andre601.ch/profiles/#hideplayershover
#
hidePlayersHover: false
#
# Set the lines shown when the player hovers over the player count.
# Unlike the MOTD option does this one only support basic color and formatting codes. Any RGB colors will be
# downsampled to the nearest matching color.
@@ -424,6 +430,14 @@ checkUpdates: true
#
debug: false
#
# Should the "ASL" ASCI art be displayed on plugin startup?
# Setting this to false will hide the banner.
#
# Default: true
#
printBanner: true
#
# Sets how long Favicons should be cached. Value is in minutes with the lowest possible value being 1.
#
@@ -460,6 +474,12 @@ disableDuringMaintenance:
#
hidePlayers: true
#
# Whether hiding player list on hover should be disabled while Maintenance mode is active.
#
# Default: true
#
hidePlayersHover: true
#
# Whether player count text changing should be disabled while Maintenance mode is active.
#
# Default: true
@@ -483,6 +503,12 @@ disableDuringMaintenance:
# Default: true
#
maxPlayers: true
#
# Whether the online players feature should be disabled while Maintenance mode is active.
#
# Default: true
#
onlinePlayers: true
#
# DO NOT EDIT!
@@ -490,7 +516,7 @@ disableDuringMaintenance:
# This is used internally to determine if the config needs to be migrated.
# Changing or even removing this option could result in your config being broken.
#
config-version: 5
configVersion: 7
```
///
+42 -3
View File
@@ -50,6 +50,12 @@ AdvancedServerList supports the following platforms:
----
A simple guide on how to get started with AdvancedServerList.
----
**Other Pages:**
- [:octicons-project-roadmap-24: Tutorials](getting-started/tutorials.md)
- [:octicons-rel-file-path-24: **Commands**](commands/index.md)
@@ -62,6 +68,14 @@ AdvancedServerList supports the following platforms:
----
Pages containing information about the Server List Profile System and all its available options.
----
**Other Pages:**
- [:octicons-file-code-24: Expressions](profiles/expressions.md)
- [:octicons-pencil-24: Formatting](profiles/formatting.md)
- [:material-percent: Placeholders](profiles/placeholders.md)
- [:octicons-list-unordered-24: **Examples**](examples/index.md)
@@ -74,12 +88,37 @@ AdvancedServerList supports the following platforms:
----
Pages explaining how to migrate from one version of the plugin to another.
----
**Other Pages:**
- Plugins
- [:octicons-arrow-right-24: MiniMOTD](migration/plugins/minimotd.md)
- [:octicons-arrow-right-24: ServerListPlus](migration/plugins/serverlistplus.md)
- Versions
- [:octicons-arrow-right-24: v3.8.0 to v3.9.0](migration/versions/v3.8.0-to-v3.9.0.md)
- [:octicons-arrow-right-24: v3.7.0 to v3.8.0](migration/versions/v3.7.0-to-v3.8.0.md)
- [:octicons-arrow-right-24: < v3.7.0 to v3.7.0](migration/versions/pre-v3.7.0-to-v3.7.0.md)
- [:octicons-arrow-right-24: v1.9.0 to v1.10.0](migration/versions/v1.9.0-to-v1.10.0.md)
- [:octicons-arrow-right-24: v1.5.3 to v1.6.0](migration/versions/v1.5.3-to-v1.6.0.md)
- [:octicons-command-palette-24: **API**](api/index.md)
----
Pages all about the API of AdvancedServerList.
----
**Other Pages:**
- [:octicons-plus-circle-24: Adding Placeholders](api/adding-placeholders.md)
- [:octicons-broadcast-24: Listening for Events](api/listening-for-events.md)
- [:octicons-log-24: Changelog](api/changelog.md)
- [:octicons-link-external-24: Javadocs](api/javadocs.md)
- [:simple-codeberg: Source Code](api/source.md)
- [:octicons-file-code-24: Reference](api/reference/index.md)
- [:octicons-plus-circle-24: **Addons**](addons/index.md)
@@ -87,10 +126,10 @@ AdvancedServerList supports the following platforms:
Pages listing 1st-party and 3rd-party add-ons for AdvancedServerList.
- [:material-percent: **PlaceholderAPI**](placeholderapi/index.md)
----
Information about available placeholders to use in PlaceholderAPI and how to use placeholders from other plugins in AdvancedServerList using PlaceholderAPI.
**Other Pages:**
- [:octicons-stop-24: BanPlugins Addon](addons/banplugins.md)
</div>
+88 -37
View File
@@ -4,57 +4,108 @@ icon: octicons/pencil-24
# Formatting
All text options, with exception of [`Conditions`](index.md#condition), allow the usage of formatting options using [MiniMessage].
This page explains specific points of formatting in AdvancedServerList, be it [text formatting](#text-formatting) or special quirks of [YAML formatting](#yaml-formatting).
If you have questions, don't hesitate to join the [M.O.S.S. Discord][discord] and ask in the `#asl-support` channel.
/// info | Online Tool
The devs of the Adventure library provide a handy online tool to create the right formatting codes to display the text properly.
You can find the tool at https://webui.advntr.dev/
## Text Formatting
AdvancedServerList uses the [MiniMessage Text Formatting][minimessage] for all formatting of text options ([`motd`](index.md#motd), [`playerCount -> hover`](index.md#hover) and [`playerCount -> text`](index.md#text)).
It supports all available [formatting tags][tags] with a [few exceptions and limitations](#exceptions-and-limitations).
/// note
Please note that the available tags in MiniMessage depend heavily on the version used on the Server or Proxy.
While AdvancedServerList downloads MiniMessage for BungeeCord, is it using the built-in version on Paper and Velocity. Due to this could the version used be older than the one the plugin is compiled against.
///
## Before starting
[minimessage]: https://docs.advntr.dev/minimessage/index.html
[tags]: https://docs.advntr.dev/minimessage/format.html
Please make sure to surround your text with either single quotes (`'`) or double quotes (`"`).
This avoids possible issues where the YAML parser would treat lines starting with specific characters as special options (i.e. `<` would be treated as a scolar value).
### Why MiniMessage and not `&`/`§`? { #why-minimessage }
Also, should your text contain single quotes (for example when using `you're`), either surround the text with double quotes, or change the single quote in your text into two single quotes (`you're -> you''re`).
The reason to only support MiniMessage and not the so-called legacy color and formatting codes can be boiled down to 3 specific reasons:
```yaml title="Wrong formatting"
motd:
- <red>This will cause
- <red>Errors
```
1. **MiniMessage tags are more clear on what they do.**
`<aqua>` makes it clear that it colors the text while `&b` is not as clear on that matter.
2. **MiniMessage allows easier combination of color and formatting codes.**
MiniMessage "remembers" what colors and formatting you applied, meaning if you close a tag and a previous one was set before it, that will continue.
/// details | Example
type: example
Below is a comparison of the same text made in MiniMessage and Color codes.
//// tab | MiniMessage
```
<aqua>Hello <green><bold>World! <red>How</bold> are</red> you?
```
////
//// tab | Color Codes
```
# We add &c after "How" to avoid a "bold space"
&bHello &a&lWorld! &c&lHow&c are &ayou?
```
////
///
3. **MiniMessage adds easier support for advanced features (RGB colors, gradients, etc.)**
With MiniMessage is it significantly more easy to use more advanced features such as gradients, rgb colors and similar.
While legacy colors have a form of RGB support is the format weird to look at and gradients become a mess. This is even worse when including bold for specific characters.
/// details | Example
type: example
Below is an example of a Gradient starting at `#084CFB` (:octicons-dot-fill-24:{ style="color: #084CFB;" }) and ending at `#ADF3FD` (:octicons-dot-fill-24:{ style="color: #ADF3FD;" }).
//// tab | MiniMessage
```
<gradient:#084CFB:#ADF3FD>Hello World!</gradient>
```
////
//// tab | Color Codes
```
&#084CFBH&#195DFBe&#296DFBl&#3A7EFCl&#4A8FFCo &#6BB0FCW&#7CC1FCo&#8CD2FDr&#9DE2FDl&#ADF3FDd
```
////
///
```yaml title="Right formatting"
motd:
- "<green>This will be formatted"
- "<green>properly."
```
In general is MiniMessage a lot easier to manage and read as legacy color codes, as their appearance is more distinct from normal text.
## Unsupported options
### Exceptions and Limitations
The following options are **not** supported, no matter what option they are used in:
Due to the situation MiniMessage is used in are not all of its features available to use.
Below is a list of all features that cannot be used by AdvancedServerList, or can only be used by it under specific conditions.
- Hover Actions (Show text, Show Advancement, etc)
- Click Actions (Run command, Suggest command, etc.)
- Custom Fonts (May work if player already has the resource pack loaded)
| Feature | Tag | Supported? |
|---------------|------------------------------|------------------------------------------------------------------------------------------------------------------------------------|
| RGB Colors | `<#rrggbb>` | Only works properly on `motd` and will be downsampled to the closest named color for the other options. |
| RGB Gradients | `<gradient:#rrggbb:#rrggbb>` | Only works properly on `motd` and will be downsampled to the closest named color for the other options. |
| Click Actions | `<click:_type_:_value_>` | Tag will be rendered, but clicking the text won't do anything. |
| Hover Actions | `<hover:_type_:_value_>` | Tag will be rendered, but hovering the text won't show anything. |
| Insertions | `<insertion:_value_>` | Tag will be rendered, but clicking the text won't do anything. |
| Font | `<font:_font_>` | May work with the default fonts (`default`, `alt` and `uniform`) and with custom ones should the client already have it. |
| Selector | `<selector:_sel_>` | Tag will be rendered, but clicking the text won't do anything. |
| Score | `<score:_name_:_objective_>` | Will not render due to requiring the player to be on the server. |
| Pride | `<pride[:_flag_]>` | Only works if the server (Paper) or Proxy (Velocity) use `v4.18.0` of MiniMessage. BungeeCord is unaffected due to downloading it. |
## Colors
## Yaml Formatting
### Default colors
YAML has some special quirks that require attention when editing the file.
Such quirks include features such as...
Default colors such as `<red>`, `<green>`, `<aqua>`, etc. may be used in all text options.
- Reading `~` and `null` as literal null values.
- Reading `>` and `|` as multi-line indicators.
### Hex Colors
To avoid this should you always surround text values with single (`'`) or double quotes (`"`) to force the YAML parser to see those as Strings.
Hexadecimal colors may be used in the [`motd`](index.md#motd) option using the `<#RRGGBB>` format.
/// note | Note on Single quotes inside text
Using single quotes, the YAML parser may get confused when the text itself also contains a single quote.
As an example `'You're banned'` would mess up the value because the `'` in `You're` is understood as the end of the String.
### Gradients
To avoid this issue, do one of the following:
Gradients can be created by using `<gradient:<color1>:<color2>>`, replacing `<color1>` with a starting color name or hex color value and `<color2>` with an ending color name or hex color value.
Only the [`motd`](index.md#motd) option may support hex color gradients and in all other options will it be downsampled.
## Formatting
Formatting options (`<bold>`, `<italic>`, etc.) are available for all text options.
[MiniMessage]: https://docs.adventure.kyori.net/minimessage/index.html
- Surround the text with Double quotes instead of Single quotes (`"You're banned"`)
- Use two single quotes (`''`) inside the text every time you want to display a single quote character (`'You''re banned'`)
///
+7
View File
@@ -198,6 +198,13 @@ playerCount:
</div>
///
### HidePlayersHover
The `playerCount -> hidePlayersHover` option takes a boolean (`true` or `false`) to set whether the hover displayed on the player count should be hidden or not.
When set to true will hovering over the player count show nothing.
In addition will [`playerCount -> hover`](#hover) not be used.
### Hover
The `playerCount -> hover` option allows you to define lines of text to show when a player hovers over the player count with their cursor.
+67 -42
View File
@@ -15,43 +15,39 @@ The following placeholders are available in AdvancedServerList itself. Please no
These placeholders use the player who pinged the server, to return values. They may require the player to be cached in order to work.
| Placeholder | Description | Platforms | Cached Player required?[^1] |
|-----------------------------|-----------------------------------------------------------------|--------------|-----------------------------|
| `${player name}` | The name of the player. | All | Yes[^2] |
| `${player protocol}` | The protocol version of the player. | All | No |
| `${player uuid}` | The UUID of the player. | All | Yes[^3] |
| `${player version}` | The protocol version of the player as readable MC version. | Velocity | No |
| `${player hasPlayedBefore}` | Boolean for whether the player has played on the server before. | Paper | Yes |
| `${player isBanned}` | Boolean for whether the player has been banned from the server. | Paper | Yes |
| `${player isWhitelisted}` | Boolean for whether the player is whitelisted on the server. | Paper | Yes |
| Placeholder | Description | Platforms | Cached Player required?^[1](#player-n1)^ |
|-----------------------------|-----------------------------------------------------------------|--------------|------------------------------------------|
| `${player name}` | The name of the player. | All | Yes^[2](#player-n2)^ |
| `${player protocol}` | The protocol version of the player. | All | No |
| `${player uuid}` | The UUID of the player. | All | Yes^[3](#player-n3)^ |
| `${player version}` | The protocol version of the player as readable MC version. | Velocity | No |
| `${player hasPlayedBefore}` | Boolean for whether the player has played on the server before. | Paper | Yes |
| `${player isBanned}` | Boolean for whether the player has been banned from the server. | Paper | Yes |
| `${player isWhitelisted}` | Boolean for whether the player is whitelisted on the server. | Paper | Yes |
[^1]:
Whether this placeholder requires the player to have joined before while AdvancedServerList was running.
When `disableCache` is enabled will these placeholders not work.
[^2]: Will default to whatever name has been provided in the `unknownPlayer -> name` option of the config.yml, when the player isn't cached yet.
[^3]: Will default to whatever name has been provided in the `unknownPlayer -> uuid` option of the config.yml, when the player isn't cached yet.
<small>^1^{ #player-n1 } When `Yes` is set, requires the player to have joined before while AdvancedServerList was running. Placeholder will not work when `disableCache` is true.</small>
<small>^2^{ #player-n2 } Will default to the value provided in `unknownPlayer -> name` of the config.yml, should the player not be cached yet.</small>
<small>^3^{ #player-n3 } Will default to the value provided in `unknownPlayer -> uuid` of the config.yml, should the player not be cached yet.</small>
### Server
These placeholders use values given by the server/proxy AdvancedServerList runs on.
| Placeholder | Description | Platforms |
|------------------------------|------------------------------------------------------------|-----------|
| `${server playersOnline}` | The number of players online on this proxy/server.[^4] | All |
| `${server playersMax}` | The total number of players that can join this server.[^5] | All |
| `${server host}` | The domain/IP the player pinged.[^6] | All |
| `${server whitelistEnabled}` | Whether the whitelist is enabled or not. | Paper |
| Placeholder | Description | Platforms |
|------------------------------|-------------------------------------------------------------------------|-----------|
| `${server playersOnline}` | The number of players online on this proxy/server.^[1](#server-n1)^ | All |
| `${server playersMax}` | The total number of players that can join this server.^[2](#server-n2)^ | All |
| `${server host}` | The domain/IP the player pinged.^[3](#server-n3)^ | All |
| `${server whitelistEnabled}` | Whether the whitelist is enabled or not. | Paper |
[^4]:
A comma-separated list of world (On Paper) or Server (On BungeeCord/Velocity) names can be provided to display the added numbers of players in these worlds/servers.
**Example:** `${server playersOnline lobby1,lobby2}` will display the collective numbers of players in the servers `lobby1` and `lobby2`.
Using the [onlinePlayers' `amount`](index.md#onlineplayers-amount) option will modify the output of this placeholder, except when used in [`condition`](index.md#condition).
[^5]:
Using either the [extraPlayers' `amount`](index.md#extraplayers-amount) or [maxPlayers' `amount`](index.md#maxplayers-amount) option will make this placeholder return the modified max players count.
Only exception to this rule is when the placeholder is used in the [`condition`](index.md#condition) option in which case it returns the actual max player count of the proxy/server.
[^6]:
An optional server name can be provided to display the IP/Domain associated with that server. This only works on BungeeCord or Velocity.
**Example:** `${server host survival}` would display the IP/Domain associated with the `survival` server.
<small>^1^{ #server-n1 } You can provide a comma-separated list of world or server names (based on platform) to return the collective count of players on these worlds/servers.
Additionally, when using [`playerCount -> onlinePlayers`](index.md#onlineplayers) will this placeholder return the modified online players count, except when used within the [`condition`](index.md#condition) option.</small>
<small>^2^{ #server-n2 } When using either [`playerCount -> extraPlayers`](index.md#extraplayers) or [`playerCount -> maxPlayers`](index.md#maxplayers) will this placeholder return the modified max players count, except when used within the [`condition`](index.md#condition) option.</small>
<small>^3^{ #server-n3 } An optional server name may be provided to display the IP/Domain associated with said server. This only works for the BungeeCord and Velocity version of the plugin.</small>
### Proxy
@@ -61,14 +57,14 @@ These placeholders are only available on the BungeeCord and Velocity versions of
Servers are pinged every 10 seconds by the plugin and cached for that time period.
///
| Placeholders | Description | Default[^7] |
|------------------------------|-------------------------------------------------------------------|-------------|
| `${proxy status <name>}` | Returns `online` or `offline` based on the Server's availability. | `offline` |
| `${proxy motd <name>}` | Returns the Server's MOTD as MiniMessage String. | `none` |
| `${proxy players <name>}` | Returns the number of online Players on the Server. | `0` |
| `${proxy maxPlayers <name>}` | Returns the max number of Players allowed to join the Server. | `0` |
| Placeholders | Description | Default^[1](#proxy-n1)^ |
|------------------------------|-------------------------------------------------------------------|-------------------------|
| `${proxy status <name>}` | Returns `online` or `offline` based on the Server's availability. | `offline` |
| `${proxy motd <name>}` | Returns the Server's MOTD as MiniMessage String. | `none` |
| `${proxy players <name>}` | Returns the number of online Players on the Server. | `0` |
| `${proxy maxPlayers <name>}` | Returns the max number of Players allowed to join the Server. | `0` |
[^7]: The default value returned, should the Server or parts of it not be available.
<small>^1^{ #proxy-n1 } The default value returned, should the Server or parts of it not be available.</small>
### Maintenance
@@ -78,13 +74,42 @@ These placeholders require the [Maintenance](https://hangar.papermc.io/kennytv/M
|-------------------------------------|----------------------------------------------------------------|
| `${maintenance maintenanceEnabled}` | Returns whether the Global maintenance mode is enabled or not. |
## Adding your own Placeholders
Thanks to the API of AdvancedServerList are you able to add your own Placeholders that can be used using the `${identifier value}` pattern.
To learn how to do this, head over to the [API page](../api/index.md) to learn more.
## PlaceholderAPI
Please visit the [PlaceholderAPI page](../placeholderapi/index.md) for how AdvancedServerList works with PlaceholderAPI.
### Own Placeholders
## Custom Placeholders
The Paper version of AdvancedServerList provides the following placeholders to use through PlaceholderAPI.
Since version 2 of AdvancedServerList are developers able to provide their own placeholders to be used in a server list profile.
The format follows the same one as the [built-in placeholders](#built-in-placeholders), meaning it is `${<identifier> <placeholder>}`.
/// info
AdvancedServerList uses the same logic to determine the value to return for a player as it does when selecting a Server List Profile.
///
If you're a developer and want to integrate your own placeholders into AdvancedServerList, read about it [here](../api/index.md).
| Placeholder | Description | Default^[1](#papi-n1)^ |
|--------------------------------------|-----------------------------------------------------------------------------------|------------------------|
| `%asl_favicon%` | Value of `favicon` when present. | `(empty string)` |
| `%asl_motd%` | Value of `motd` when present. | `(empty string)` |
| `%asl_playercount_extraplayers%` | Value of `playerCount -> extraPlayers -> amount`. | `(empty string)` |
| `%asl_playercount_hideplayers%` | Returns wether `playerCount -> hidePlayers` is enabled or not. | `false` |
| `%asl_playercount_hideplayershover%` | Returns wether `playerCount -> hidePlayersHover` is enabled or not. | `false` |
| `%asl_playercount_hover%` | Value of `playerCount -> hover`. | `(empty string)` |
| `%asl_playercount_maxplayers%` | Value of `playerCount -> maxPlayers -> amount` | `(empty string)` |
| `%asl_playercount_onlineplayers%` | Value of `playerCount -> onlinePlayers -> amount` | `(empty string)` |
| `%asl_playercount_text%` | Value of `playerCount -> text` | `(empty string)` |
| `%asl_playersmax%` | Returns the max players based on currently set maxPlayers or extraPlayers values. | `(empty string)` |
<small>^1^{ #papi-n1 } The default value is returned when the option in the chosen profile is either not set, or `null`.</small>
### Other Placeholders
Placeholders from PlaceholderAPI Expansions can be used in AdvancedServerList using the default `%identifier_value%` format.
PlaceholderAPI placeholders can be used both on the Server (Paper) and on the proxy (BungeeCord, Velocity) version of AdvancedServerList.
However, the Proxy version requires the usage of [PAPIProxyBridge] to function properly.
Placeholders can also not be used within the [`condition`](index.md#condition) option of a Server List Profile.
+7 -2
View File
@@ -80,6 +80,9 @@ plugins:
'migration/pre-v3_7_0-to-v3_7_0.md': 'migration/versions/pre-v3.7.0-to-v3.7.0.md'
'migration/v1_9_0-to-v1_10_0.md': 'migration/versions/v1.9.0-to-v1.10.0.md'
'migration/v1_5_3-to-v1_6_0.md': 'migration/versions/v1.5.3-to-v1.6.0.md'
#
# PlaceholderAPI page
'placeholderapi/index.md': 'profiles/placeholders.md#own-placeholders'
- pagenav-generator
markdown_extensions:
@@ -115,6 +118,7 @@ markdown_extensions:
- pymdownx.blocks.tab:
alternate_style: true
- pymdownx.blocks.details:
- pymdownx.caret:
hooks:
- docs/.hooks/badges.py
@@ -145,6 +149,8 @@ nav:
- v1.5.3 to v1.6.0: migration/versions/v1.5.3-to-v1.6.0.md
- API:
- api/index.md
- Adding Placeholders: api/adding-placeholders.md
- Listening for Events: api/listening-for-events.md
- Changelog: api/changelog.md
- Javadocs: api/javadocs.md
- Source Code: api/source.md
@@ -196,5 +202,4 @@ nav:
- api/reference/platform-velocity/ch.andre601.advancedserverlist.api.velocity/objects/velocityproxy.md
- Addons:
- addons/index.md
- BanPlugins: addons/banplugins.md
- PlaceholderAPI: placeholderapi/index.md
- BanPlugins: addons/banplugins.md
+1 -1
View File
@@ -99,7 +99,7 @@
<dependency>
<groupId>com.viaversion</groupId>
<artifactId>viaversion-api</artifactId>
<version>5.2.0</version>
<version>5.2.1</version>
<scope>provided</scope>
</dependency>
@@ -89,9 +89,9 @@ public class PaperCore extends JavaPlugin implements PluginCore<CachedServerIcon
@Override
public void loadCommands(){
if(getServer().getCommandMap().register("asl", new CmdAdvancedServerList(this))){
getPluginLogger().info("Registered /advancedserverlist:advancedserverlist");
getPluginLogger().success("Registered <white>/advancedserverlist:advancedserverlist</white>!");
}else{
getPluginLogger().info("Registered /asl:advancedserverlist");
getPluginLogger().success("Registered <white>/asl:advancedserverlist</white>!");
}
}
@@ -222,7 +222,7 @@ public class PaperCore extends JavaPlugin implements PluginCore<CachedServerIcon
OptionalInt build = info.buildNumber();
if(build.isEmpty())
return String.format("%s (%s, Version: %s)", name, id, version);
return String.format("%s (ID: %s, Version: %s)", name, id, version);
return String.format("%s (ID: %s, Version: %s, Build: %d)", name, id, version, build.getAsInt());
}
@@ -98,14 +98,24 @@ public class PaperEventWrapper implements GenericEventWrapper<CachedServerIcon,
@Override
public void setPlayers(List<String> lines, PaperPlayerImpl player, GenericServer server){
try{
event.getListedPlayers();
Class.forName("com.destroystokyo.paper.event.server.PaperServerListPingEvent$ListedPlayerInfo");
setListedPlayers(lines, player, server);
}catch(NoSuchMethodError ex){
}catch(ClassNotFoundException ex){
// Version before latest 1.20.6 builds(?)
setPlayerSample(lines, player, server);
}
}
@Override
public void setPlayersHidden(){
try{
Class.forName("com.destroystokyo.paper.event.server.PaperServerListPingEvent$ListedPlayerInfo");
clearListedPlayers();
}catch(ClassNotFoundException ex){
clearPlayerSample();
}
}
@Override
public void setFavicon(CachedServerIcon favicon){
event.setServerIcon(favicon);
@@ -184,8 +194,12 @@ public class PaperEventWrapper implements GenericEventWrapper<CachedServerIcon,
return new PaperServerImpl(plugin.getWorldCache().worlds(), playersOnline, playersMax, host);
}
private void setListedPlayers(List<String> lines, PaperPlayerImpl player, GenericServer server){
private void clearListedPlayers(){
event.getListedPlayers().clear();
}
private void setListedPlayers(List<String> lines, PaperPlayerImpl player, GenericServer server){
clearListedPlayers();
List<PaperServerListPingEvent.ListedPlayerInfo> playerList = getPlayerList(lines, player, server,
text -> new PaperServerListPingEvent.ListedPlayerInfo(text, UUID.randomUUID()));
@@ -193,8 +207,13 @@ public class PaperEventWrapper implements GenericEventWrapper<CachedServerIcon,
}
@SuppressWarnings({"Deprecation", "removal"})
private void setPlayerSample(List<String> lines, PaperPlayerImpl player, GenericServer server){
private void clearPlayerSample(){
event.getPlayerSample().clear();
}
@SuppressWarnings({"Deprecation", "removal"})
private void setPlayerSample(List<String> lines, PaperPlayerImpl player, GenericServer server){
clearPlayerSample();
List<PlayerProfile> playerProfiles = getPlayerList(lines, player, server, FakePlayerProfile::create);
event.getPlayerSample().addAll(playerProfiles);
@@ -26,19 +26,18 @@
package ch.andre601.advancedserverlist.paper.logging;
import ch.andre601.advancedserverlist.core.interfaces.PluginLogger;
import ch.andre601.advancedserverlist.core.parsing.ComponentParser;
import ch.andre601.advancedserverlist.paper.PaperCore;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
public class PaperLogger implements PluginLogger{
private final PaperCore plugin;
private final Logger logger;
private final ComponentLogger logger;
public PaperLogger(PaperCore plugin){
this.plugin = plugin;
this.logger = plugin.getLogger();
this.logger = plugin.getComponentLogger();
}
@Override
@@ -55,21 +54,21 @@ public class PaperLogger implements PluginLogger{
@Override
public void info(String msg, Object... args){
logger.info(String.format(msg, args));
logger.info(ComponentParser.textFormatted("<grey>" + msg, args).toComponent());
}
@Override
public void warn(String msg, Object... args){
logger.warning(String.format(msg, args));
logger.warn(ComponentParser.textFormatted("<yellow>" + msg, args).toComponent());
}
@Override
public void warn(String msg, Throwable throwable){
logger.log(Level.WARNING, msg, throwable);
logger.warn(ComponentParser.text("<yellow>" + msg).toComponent(), throwable);
}
@Override
public void warn(String msg, Throwable throwable, Object... args){
logger.log(Level.WARNING, String.format(msg, args), throwable);
logger.warn(ComponentParser.textFormatted("<yellow>" + msg, args).toComponent(), throwable);
}
}
@@ -36,7 +36,7 @@ public class PaperPlayerImpl implements BukkitPlayer{
private final OfflinePlayer player;
private final String name;
private int protocol;
private final int protocol;
private final UUID uuid;
private boolean playedBefore = false;
@@ -115,13 +115,15 @@ public class PAPIPlaceholders extends PlaceholderExpansion{
BukkitServer finalServer = new PaperServerImpl(plugin.getWorldCache().worlds(), online, max, host);
return switch(identifier.toLowerCase(Locale.ROOT)){
case "motd" -> getOption(entry.motd(), pl, player, finalServer, true);
case "favicon" -> getOption(entry.favicon(), pl, player, finalServer);
case "motd" -> getOption(entry.motd(), pl, player, finalServer, true);
case "playercount_extraplayers" -> extraPlayers == null ? "" : String.valueOf(extraPlayers);
case "playercount_hideplayers" -> String.valueOf(entry.hidePlayersEnabled().getOrDefault(false));
case "playercount_hideplayershover" -> String.valueOf(entry.hidePlayersHoverEnabled().getOrDefault(false));
case "playercount_hover" -> getOption(entry.players(), pl, player, finalServer, false);
case "playercount_maxplayers" -> maxPlayers == null ? "" : String.valueOf(maxPlayers);
case "playercount_onlineplayers" -> onlinePlayers == null ? "" : String.valueOf(onlinePlayers);
case "playercount_text" -> getOption(entry.playerCountText(), pl, player, finalServer);
case "playercount_extraplayers" -> extraPlayers == null ? "null" : String.valueOf(extraPlayers);
case "playercount_maxplayers" -> maxPlayers == null ? "null" : String.valueOf(maxPlayers);
case "playercount_onlineplayers" -> onlinePlayers == null ? "null" : String.valueOf(onlinePlayers);
case "server_playersmax" -> String.valueOf(max);
default -> null;
};
@@ -137,10 +139,16 @@ public class PAPIPlaceholders extends PlaceholderExpansion{
}
private String getOption(String str, Player pl, BukkitPlayer player, BukkitServer server){
if(str == null)
return "";
return getOption(Collections.singletonList(str), pl, player, server, false);
}
private String getOption(List<String> list, Player pl, BukkitPlayer player, BukkitServer server, boolean isMotd){
if(list.isEmpty())
return "";
return list.stream()
.map(line -> ComponentParser.text(line)
.modifyText(text -> StringReplacer.replace(text, player, server))
+6 -5
View File
@@ -37,14 +37,14 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<plugin.version>5.2.0</plugin.version>
<plugin.version>5.4.1</plugin.version>
<plugin.description>Create multiple Server lists based on conditions.</plugin.description>
<api.version>4.0.0</api.version>
<api.version>4.1.0</api.version>
<dependency.adventure-api>4.17.0</dependency.adventure-api>
<dependency.adventure-text-minimessage>4.17.0</dependency.adventure-text-minimessage>
<dependency.adventure-text-serializer-legacy>4.17.0</dependency.adventure-text-serializer-legacy>
<dependency.adventure-api>4.18.0</dependency.adventure-api>
<dependency.adventure-text-minimessage>4.18.0</dependency.adventure-text-minimessage>
<dependency.adventure-text-serializer-legacy>4.18.0</dependency.adventure-text-serializer-legacy>
<dependency.adventure-platform-bungeecord>4.3.4</dependency.adventure-platform-bungeecord>
<dependency.configurate-yaml>4.1.2</dependency.configurate-yaml>
@@ -74,5 +74,6 @@
<module>velocity</module>
<module>version-uploader</module>
<module>addons/banplugins</module>
<module>addons/placeholderapi</module>
</modules>
</project>
+3 -1
View File
@@ -1,5 +1,7 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:base", "group:allNonMajor"],
"dependencyDashboard": false,
"labels": ["PR Type/Dependency Update"],
"ignoreDeps": [
"ch.andre601.advancedserverlist:parent",
@@ -37,7 +39,7 @@
"io.papermc.paper:paper-api"
],
"registryUrls": [
"https://papermc.io/repo/repository/maven-public"
"https://repo.papermc.io/repository/maven-public"
]
},
{
+17 -1
View File
@@ -48,6 +48,10 @@
<id>velocity-repo</id>
<url>https://repo.papermc.io/repository/maven-public/</url>
</repository>
<repository>
<id>repo-sayandevelopment</id>
<url>https://repo.sayandev.org/snapshots</url>
</repository>
</repositories>
<dependencies>
@@ -68,7 +72,7 @@
<dependency>
<groupId>com.velocitypowered</groupId>
<artifactId>velocity-api</artifactId>
<version>3.1.2-SNAPSHOT</version>
<version>3.4.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
@@ -95,6 +99,18 @@
<version>2.9.0-4</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.sayandev</groupId>
<artifactId>sayanvanish-api</artifactId>
<version>1.6.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.sayandev</groupId>
<artifactId>sayanvanish-proxy-velocity</artifactId>
<version>1.6.1</version>
<scope>provided</scope>
</dependency>
<!-- Other -->
<dependency>
@@ -30,14 +30,17 @@ import ch.andre601.advancedserverlist.core.interfaces.PluginLogger;
import ch.andre601.advancedserverlist.core.interfaces.core.PluginCore;
import ch.andre601.advancedserverlist.core.profiles.handlers.FaviconHandler;
import ch.andre601.advancedserverlist.velocity.commands.CmdAdvancedServerList;
import ch.andre601.advancedserverlist.velocity.listeners.JoinEvent;
import ch.andre601.advancedserverlist.velocity.listeners.PingEvent;
import ch.andre601.advancedserverlist.velocity.listeners.JoinListener;
import ch.andre601.advancedserverlist.velocity.listeners.PingListener;
import ch.andre601.advancedserverlist.velocity.listeners.PluginMessageListener;
import ch.andre601.advancedserverlist.velocity.logging.VelocityLogger;
import ch.andre601.advancedserverlist.velocity.objects.placeholders.VelocityPlayerPlaceholders;
import ch.andre601.advancedserverlist.velocity.objects.placeholders.VelocityProxyPlaceholders;
import ch.andre601.advancedserverlist.velocity.objects.placeholders.VelocityServerPlaceholders;
import com.alessiodp.libby.Library;
import com.alessiodp.libby.VelocityLibraryManager;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import com.google.inject.Inject;
import com.velocitypowered.api.command.CommandMeta;
import com.velocitypowered.api.event.Subscribe;
@@ -46,15 +49,16 @@ import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
import com.velocitypowered.api.plugin.annotation.DataDirectory;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerPing;
import com.velocitypowered.api.scheduler.ScheduledTask;
import com.velocitypowered.api.util.Favicon;
import de.myzelyam.api.vanish.VelocityVanishAPI;
import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
import org.bstats.charts.SimplePie;
import org.bstats.velocity.Metrics;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sayandev.sayanvanish.velocity.api.SayanVanishVelocityAPI;
import java.awt.image.BufferedImage;
import java.nio.file.Path;
@@ -67,10 +71,12 @@ import java.util.concurrent.TimeUnit;
public class VelocityCore implements PluginCore<Favicon>{
private final Logger logger = LoggerFactory.getLogger("AdvancedServerList");
public static final MinecraftChannelIdentifier ASL_IDENTIFIER = MinecraftChannelIdentifier.from("advancedserverlist:action");
private final Map<String, ServerPing> fetchedServers = new ConcurrentHashMap<>();
private final PluginLogger pluginLogger;
private final ComponentLogger logger;
private final ProxyServer proxy;
private final Path path;
private final Metrics.Factory metrics;
@@ -84,7 +90,10 @@ public class VelocityCore implements PluginCore<Favicon>{
@Inject
public VelocityCore(ProxyServer proxy, @DataDirectory Path path, Metrics.Factory metrics){
this.pluginLogger = new VelocityLogger(this, this.logger);
proxy.getChannelRegistrar().register(ASL_IDENTIFIER);
ComponentLogger logger = ComponentLogger.logger("AdvancedServerList");
this.pluginLogger = new VelocityLogger(this, logger);
this.logger = logger;
this.proxy = proxy;
this.path = path;
@@ -112,12 +121,14 @@ public class VelocityCore implements PluginCore<Favicon>{
.build();
getProxy().getCommandManager().register(command, new CmdAdvancedServerList(this));
getPluginLogger().success("Registered <white>/advancedserverlist</white>!");
}
@Override
public void loadEvents(){
new JoinEvent(this);
new PingEvent(this);
new JoinListener(this);
new PingListener(this);
new PluginMessageListener(this);
}
@Override
@@ -223,6 +234,9 @@ public class VelocityCore implements PluginCore<Favicon>{
// Exclude players when PremiumVanish is enabled and player is hidden.
if(getProxy().getPluginManager().isLoaded("premiumvanish")){
players.removeIf(VelocityVanishAPI::isInvisible);
}else // Do the same if SayanVanish is enabled.
if(getProxy().getPluginManager().isLoaded("sayanvanish")){
players.removeIf(player -> SayanVanishVelocityAPI.getInstance().isVanished(player.getUniqueId()));
}
return players.size();
@@ -239,6 +253,11 @@ public class VelocityCore implements PluginCore<Favicon>{
return;
fetchedServers.put(server.getServerInfo().getName(), ping);
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("findPlugins");
server.sendPluginMessage(ASL_IDENTIFIER, out.toByteArray());
}));
}
}
@@ -33,11 +33,11 @@ import com.velocitypowered.api.proxy.Player;
import java.net.InetSocketAddress;
public class JoinEvent{
public class JoinListener{
private final VelocityCore plugin;
public JoinEvent(VelocityCore plugin){
public JoinListener(VelocityCore plugin){
this.plugin = plugin;
plugin.getProxy().getEventManager().register(plugin, this);
}
@@ -33,11 +33,11 @@ import com.velocitypowered.api.event.PostOrder;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.proxy.ProxyPingEvent;
public class PingEvent{
public class PingListener{
private final VelocityCore plugin;
public PingEvent(VelocityCore plugin){
public PingListener(VelocityCore plugin){
this.plugin = plugin;
plugin.getProxy().getEventManager().register(plugin, this);
}
@@ -0,0 +1,40 @@
package ch.andre601.advancedserverlist.velocity.listeners;
import ch.andre601.advancedserverlist.core.objects.PluginMessageUtil;
import ch.andre601.advancedserverlist.velocity.VelocityCore;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteStreams;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.PluginMessageEvent;
import com.velocitypowered.api.proxy.ServerConnection;
public class PluginMessageListener{
public PluginMessageListener(VelocityCore plugin){
plugin.getProxy().getEventManager().register(plugin, this);
}
@Subscribe
public void onPluginMessage(PluginMessageEvent event){
if(!(event.getSource() instanceof ServerConnection))
return;
if(event.getIdentifier() != VelocityCore.ASL_IDENTIFIER)
return;
ByteArrayDataInput input = ByteStreams.newDataInput(event.getData());
String subChannel = input.readUTF();
switch(subChannel){
case "hasPlugin" -> {
String serverName = input.readUTF();
PluginMessageUtil.get().addServer(serverName);
}
case "parsed" -> {
String uuid = input.readUTF();
String parsed = input.readUTF();
PluginMessageUtil.get().queue(uuid, parsed, true);
}
}
}
}
@@ -29,17 +29,18 @@ import ch.andre601.advancedserverlist.api.events.GenericServerListEvent;
import ch.andre601.advancedserverlist.api.objects.GenericServer;
import ch.andre601.advancedserverlist.api.profiles.ProfileEntry;
import ch.andre601.advancedserverlist.core.compat.maintenance.MaintenanceUtil;
import ch.andre601.advancedserverlist.core.compat.papi.PAPIUtil;
import ch.andre601.advancedserverlist.core.interfaces.core.PluginCore;
import ch.andre601.advancedserverlist.core.interfaces.events.GenericEventWrapper;
import ch.andre601.advancedserverlist.core.objects.CachedPlayer;
import ch.andre601.advancedserverlist.core.objects.PluginMessageUtil;
import ch.andre601.advancedserverlist.core.parsing.ComponentParser;
import ch.andre601.advancedserverlist.core.profiles.replacer.StringReplacer;
import ch.andre601.advancedserverlist.velocity.VelocityCore;
import ch.andre601.advancedserverlist.velocity.objects.impl.VelocityPlayerImpl;
import ch.andre601.advancedserverlist.velocity.objects.impl.VelocityProxyImpl;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import com.velocitypowered.api.event.proxy.ProxyPingEvent;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerPing;
import com.velocitypowered.api.util.Favicon;
@@ -119,6 +120,11 @@ public class VelocityEventWrapper implements GenericEventWrapper<Favicon, Veloci
builder.clearSamplePlayers().samplePlayers(players);
}
@Override
public void setPlayersHidden(){
builder.clearSamplePlayers();
}
@Override
public void setFavicon(Favicon favicon){
builder.favicon(favicon);
@@ -172,25 +178,25 @@ public class VelocityEventWrapper implements GenericEventWrapper<Favicon, Veloci
@Override
public String parsePAPIPlaceholders(String text, VelocityPlayerImpl player){
if(!plugin.getProxy().getPluginManager().isLoaded("papiproxybridge"))
return text;
if(!PAPIUtil.get().isCompatible())
return text;
String server = PAPIUtil.get().getServer();
if(server == null || server.isEmpty())
return text;
RegisteredServer registeredServer = plugin.getProxy().getServer(server).orElse(null);
if(registeredServer == null || registeredServer.getPlayersConnected().isEmpty())
return text;
Player carrier = PAPIUtil.get().getPlayer(registeredServer.getPlayersConnected());
if(carrier == null)
return text;
return PAPIUtil.get().parse(text, carrier.getUniqueId(), player.getUUID());
return PluginMessageUtil.get().getOrExecute(player.getUUID().toString(), text, (uuid, textValue) -> {
List<String> servers = PluginMessageUtil.get().getKnownServers();
if(servers.isEmpty())
return textValue;
RegisteredServer registeredServer = plugin.getProxy().getServer(servers.get(0)).orElse(null);
if(registeredServer == null)
return textValue;
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("parse");
out.writeUTF(uuid);
out.writeUTF(textValue);
registeredServer.sendPluginMessage(VelocityCore.ASL_IDENTIFIER, out.toByteArray());
PluginMessageUtil.get().queue(uuid, textValue, false);
return PluginMessageUtil.get().getOrDefault(uuid, textValue);
});
}
@Override
@@ -26,15 +26,16 @@
package ch.andre601.advancedserverlist.velocity.logging;
import ch.andre601.advancedserverlist.core.interfaces.PluginLogger;
import ch.andre601.advancedserverlist.core.parsing.ComponentParser;
import ch.andre601.advancedserverlist.velocity.VelocityCore;
import org.slf4j.Logger;
import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
public class VelocityLogger implements PluginLogger{
private final VelocityCore plugin;
private final Logger logger;
private final ComponentLogger logger;
public VelocityLogger(VelocityCore plugin, Logger logger){
public VelocityLogger(VelocityCore plugin, ComponentLogger logger){
this.plugin = plugin;
this.logger = logger;
}
@@ -53,21 +54,21 @@ public class VelocityLogger implements PluginLogger{
@Override
public void info(String msg, Object... args){
logger.info(String.format(msg, args));
logger.info(ComponentParser.textFormatted("<grey>" + msg, args).toComponent());
}
@Override
public void warn(String msg, Object... args){
logger.warn(String.format(msg, args));
logger.warn(ComponentParser.textFormatted(msg, args).toComponent());
}
@Override
public void warn(String msg, Throwable throwable){
logger.warn(msg, throwable);
logger.warn(ComponentParser.text(msg).toComponent(), throwable);
}
@Override
public void warn(String msg, Throwable throwable, Object... args){
logger.warn(String.format(msg, args), throwable);
logger.warn(ComponentParser.textFormatted(msg, args).toComponent(), throwable);
}
}
+1 -1
View File
@@ -52,7 +52,7 @@
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.14</version>
<version>1.5.16</version>
<scope>compile</scope>
</dependency>
<dependency>