Compare commits

..
187 changed files with 3344 additions and 5599 deletions

No files matched your search

+2 -2
View File
@@ -18,13 +18,13 @@ jobs:
runs-on: codeberg-tiny
steps:
- name: "Checkout Source Repository"
uses: https://code.forgejo.org/actions/checkout@v6
uses: https://code.forgejo.org/actions/checkout@v4
with:
token: "${{ secrets.BOT_TOKEN }}"
- name: "Checkout Target Repository"
run: "git clone -b pages https://codeberg.org/Andre601/asl-docs site"
- name: "Setup Python 3.x"
uses: https://code.forgejo.org/actions/setup-python@v6
uses: https://code.forgejo.org/actions/setup-python@v5
with:
python-version: 3.x
- name: "Install Dependencies"
+7 -7
View File
@@ -28,13 +28,13 @@ jobs:
#
- name: "Find Comment"
id: find-comment
uses: https://github.com/peter-evans/find-comment@v4
uses: https://github.com/peter-evans/find-comment@v3
with:
issue-number: "${{ github.event.pull_request.number }}"
comment-author: "Andre_601-Bot"
- name: "Create or update Comment"
id: comment
uses: https://github.com/peter-evans/create-or-update-comment@v5
uses: https://github.com/peter-evans/create-or-update-comment@v4
with:
body: |-
## Pull request Preview
@@ -49,7 +49,7 @@ jobs:
# Build website and push.
#
- name: "Checkout Source Repository"
uses: https://code.forgejo.org/actions/checkout@v6
uses: https://code.forgejo.org/actions/checkout@v4
with:
token: ${{ secrets.BOT_TOKEN }}
fetch-depth: 0
@@ -60,7 +60,7 @@ jobs:
cd site
git switch ${{ env.BRANCH_NAME }} 2>/dev/null || git switch -c ${{ env.BRANCH_NAME }}
- name: "Setup Python 3.x"
uses: https://code.forgejo.org/actions/setup-python@v6
uses: https://code.forgejo.org/actions/setup-python@v5
with:
python-version: 3.x
- name: "Install Dependencies"
@@ -81,7 +81,7 @@ jobs:
# Update comment on success or failure.
#
- name: "Comment Success"
uses: https://github.com/peter-evans/create-or-update-comment@v5
uses: https://github.com/peter-evans/create-or-update-comment@v4
with:
body: |-
## Pull request Preview
@@ -99,7 +99,7 @@ jobs:
edit-mode: replace
- name: "Comment Failure"
if: ${{ failure() }}
uses: https://github.com/peter-evans/create-or-update-comment@v5
uses: https://github.com/peter-evans/create-or-update-comment@v4
with:
body: |-
## Pull request Preview
@@ -109,7 +109,7 @@ jobs:
| Name | Link |
|---------|-------------------------------------------|
| Commit | ${{ github.event.pull_request.head.sha }} |
| Logs | ${{ env.RUN_URL }} |
| Logs | ${{ RUN_URL }} |
comment-id: "${{ steps.comment.outputs.comment-id }}"
issue-number: "${{ github.event.pull_request.number }}"
token: "${{ secrets.BOT_TOKEN }}"
+2 -18
View File
@@ -4,7 +4,7 @@ when:
steps:
- name: "Build Jar and Upload"
image: maven:3.9.12-eclipse-temurin-21
image: maven:3.9.9-eclipse-temurin-21
environment:
CODEBERG_API_TOKEN:
from_secret: cb_token
@@ -29,7 +29,6 @@ steps:
- "paper/target/AdvancedServerList-*.jar"
- "velocity/target/AdvancedServerList-*.jar"
- "addons/banplugins/target/AdvancedServerList-*.jar"
- "addons/luckperms/target/AdvancedServerList-*.jar"
api_key:
from_secret: cb_token
- name: "Create Post on Mastodon"
@@ -46,19 +45,4 @@ steps:
Modrinth - https://modrinth.com/plugin/advancedserverlist
Hangar - https://hangar.papermc.io/Andre_601/AdvancedServerList
Codeberg - https://codeberg.org/Andre601/AdvancedServerList
visibility: public
- name: "Create Post on Bluesky"
image: woodpeckerci/plugin-bluesky-post
when:
- status: ["success"]
settings:
identifier: andre601.ch
app_password:
from_secret: bluesky_app_password
languages: english
text: |-
#AdvancedServerList was updated to ${CI_COMMIT_TAG} and can be downloaded from #Modrinth, #Hangar and #Codeberg!
Modrinth - https://modrinth.com/plugin/advancedserverlist
Hangar - https://hangar.papermc.io/Andre_601/AdvancedServerList
Codeberg - https://codeberg.org/Andre601/AdvancedServerList
visibility: public
+2 -8
View File
@@ -40,7 +40,7 @@ A Server List Profile is a single YAML file located in the plugin's `profiles` f
Each file has a priority and optional condition. The priority is used to determine what file AdvancedServerList should use first - starting from highest number to lowest - with conditions being used to set when the profile should be used.
Once a profile has been found with a condition returning true - which is the default should there be no condition - will AdvancedServerList use said profile.
> *Read more about [Priority](https://asl.andre601.ch/profiles/options/priority) and [Conditions](https://asl.andre601.ch/profiles/options/condition)*
> *Read more about [Priority](https://asl.andre601.ch/profiles/#priority) and [Conditions](https://asl.andre601.ch/profiles/#condition)*
### Placeholders
@@ -63,7 +63,7 @@ AdvancedServerList soft-depends on the following plugins, meaning their inclusio
| Plugin | Supported Platforms |
|-------------------------------------------------------------------------|----------------------------------------|
| [PlaceholderAPI](https://modrinth.com/plugin/PlaceholderAPI) | Paper |
| [PlaceholderAPI](https://hangar.papermc.io/HelpChat/PlaceholderAPI) | Paper |
| [ViaVersion](https://hangar.papermc.io/ViaVersion/ViaVersion) | Paper |
| [PAPIProxyBridge](https://hangar.papermc.io/William278/PAPIProxyBridge) | BungeeCord, Waterfall, Velocity |
| [Maintenance](https://hangar.papermc.io/kennytv/Maintenance) | Paper, BungeeCord, Waterfall, Velocity |
@@ -92,12 +92,6 @@ English Video by KasaiSora
Spanish Video by Ajneb97
[![video thumbnail](https://img.youtube.com/vi/rIbljm_4HVI/0.jpg)](https://youtube.com/watch?v=rIbljm_4HVI)
Indonesian Video by faajrikha
[![video thumbnail](https://img.youtube.com/vi/-p938rOmqy4/0.jpg)](https://www.youtube.com/watch?v=-p938rOmqy4)
German Video by Finni Gamer
[![video thumbnail](https://img.youtube.com/vi/1Kh3cPMDd0c/0.jpg)](https://www.youtube.com/watch?v=1Kh3cPMDd0c)
</p>
</details>
+14 -7
View File
@@ -40,8 +40,8 @@
<version>${plugin.version}</version>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<maven.compiler.source>16</maven.compiler.source>
<maven.compiler.target>16</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
@@ -65,14 +65,14 @@
<dependency>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-api</artifactId>
<version>1.21-R0.4</version>
<version>1.20-R0.2</version>
<type>jar</type>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-api</artifactId>
<version>1.21-R0.4</version>
<version>1.20-R0.2</version>
<type>javadoc</type>
<scope>provided</scope>
</dependency>
@@ -99,13 +99,13 @@
<dependency>
<groupId>space.arim.libertybans</groupId>
<artifactId>bans-api</artifactId>
<version>1.1.2</version>
<version>1.1.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.gitlab.ruany</groupId>
<artifactId>LiteBansAPI</artifactId>
<version>0.6.1</version>
<version>0.5.0</version>
<scope>provided</scope>
</dependency>
<dependency>
@@ -117,12 +117,19 @@
</dependencies>
<build>
<finalName>AdvancedServerList-BanPlugins-Addon-${plugin.version}${project.build.build_id}</finalName>
<finalName>AdvancedServerList-BanPlugins-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>
-112
View File
@@ -1,112 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ MIT License
~
~ Copyright (c) 2022-2024 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.
~
-->
<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>luckperms</artifactId>
<packaging>jar</packaging>
<version>${plugin.version}</version>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<repositories>
<repository>
<id>sonatype</id>
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
</repository>
<repository>
<id>papermc</id>
<url>https://repo.papermc.io/repository/maven-public/</url>
</repository>
</repositories>
<dependencies>
<!-- Proxy/Server -->
<dependency>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-api</artifactId>
<version>1.21-R0.4</version>
<type>jar</type>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-api</artifactId>
<version>1.21-R0.4</version>
<type>javadoc</type>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.papermc.paper</groupId>
<artifactId>paper-api</artifactId>
<version>1.21-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.velocitypowered</groupId>
<artifactId>velocity-api</artifactId>
<version>3.3.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<!-- Plugin APIs -->
<dependency>
<groupId>ch.andre601.asl-api</groupId>
<artifactId>api</artifactId>
<version>${api.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.luckperms</groupId>
<artifactId>api</artifactId>
<version>5.4</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>AdvancedServerList-LuckPerms-Addon-${plugin.version}${project.build.build_id}</finalName>
<resources>
<resource>
<filtering>true</filtering>
<directory>src/main/resources</directory>
</resource>
</resources>
</build>
</project>
@@ -1,39 +0,0 @@
package ch.andre601.advancedserverlist.luckperms.bungee;
import ch.andre601.advancedserverlist.api.AdvancedServerListAPI;
import ch.andre601.advancedserverlist.api.exceptions.InvalidPlaceholderProviderException;
import ch.andre601.advancedserverlist.luckperms.placeholders.LuckPermsPlaceholder;
import net.luckperms.api.LuckPerms;
import net.luckperms.api.LuckPermsProvider;
import net.md_5.bungee.api.plugin.Plugin;
public class LuckPermsAddon extends Plugin{
@Override
public void onEnable(){
if(getProxy().getPluginManager().getPlugin("AdvancedServerList") == null){
getLogger().warning("AdvancedServerList not found. This plugin is required.");
return;
}
if(getProxy().getPluginManager().getPlugin("LuckPerms") == null){
getLogger().warning("LuckPerms not found. This plugin is required.");
return;
}
LuckPerms luckPerms;
try{
luckPerms = LuckPermsProvider.get();
}catch(IllegalStateException ex){
getLogger().severe("Unable to obtain LuckPerms API instance! The Plugin won't work.");
return;
}
getLogger().info("Registering LuckPerms Placeholders...");
try{
AdvancedServerListAPI.get().addPlaceholderProvider(new LuckPermsPlaceholder(luckPerms));
getLogger().info("Successfully registered Placeholder!");
}catch(InvalidPlaceholderProviderException ex){
getLogger().warning("Couldn't register Placeholder. The plugin won't work.");
}
}
}
@@ -1,44 +0,0 @@
package ch.andre601.advancedserverlist.luckperms.paper;
import ch.andre601.advancedserverlist.api.AdvancedServerListAPI;
import ch.andre601.advancedserverlist.api.exceptions.InvalidPlaceholderProviderException;
import ch.andre601.advancedserverlist.luckperms.placeholders.LuckPermsPlaceholder;
import net.luckperms.api.LuckPerms;
import net.luckperms.api.LuckPermsProvider;
import org.bukkit.plugin.java.JavaPlugin;
public class LuckPermsAddon extends JavaPlugin{
@Override
public void onEnable(){
if(!this.getServer().getPluginManager().isPluginEnabled("AdvancedServerList")){
this.getSLF4JLogger().warn("AdvancedServerList not enabled. This plugin is required!");
this.getServer().getPluginManager().disablePlugin(this);
return;
}
if(!this.getServer().getPluginManager().isPluginEnabled("LuckPerms")){
this.getSLF4JLogger().warn("LuckPerms not enabled. This plugin is required!");
this.getServer().getPluginManager().disablePlugin(this);
return;
}
LuckPerms luckPerms;
try{
luckPerms = LuckPermsProvider.get();
}catch(IllegalStateException ex){
this.getSLF4JLogger().error("Unable to obtain LuckPerms API instance! Disabling plugin...");
this.getServer().getPluginManager().disablePlugin(this);
return;
}
this.getSLF4JLogger().info("Registering LuckPerms Placeholder...");
try{
AdvancedServerListAPI.get().addPlaceholderProvider(new LuckPermsPlaceholder(luckPerms));
this.getSLF4JLogger().info("Successfully registered Placeholder!");
}catch(InvalidPlaceholderProviderException ex){
this.getSLF4JLogger().warn("Couldn't register Placeholder. Disabling Plugin...");
this.getServer().getPluginManager().disablePlugin(this);
}
}
}
@@ -1,66 +0,0 @@
package ch.andre601.advancedserverlist.luckperms.placeholders;
import ch.andre601.advancedserverlist.api.PlaceholderProvider;
import ch.andre601.advancedserverlist.api.objects.GenericPlayer;
import ch.andre601.advancedserverlist.api.objects.GenericServer;
import net.luckperms.api.LuckPerms;
import net.luckperms.api.model.user.User;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
import java.util.HashMap;
import java.util.UUID;
public class LuckPermsPlaceholder extends PlaceholderProvider{
private final LuckPerms luckPerms;
private final HashMap<UUID, CacheValue> cache = new HashMap<>();
public LuckPermsPlaceholder(LuckPerms luckPerms){
super("luckperms");
this.luckPerms = luckPerms;
}
@Override
public @Nullable String parsePlaceholder(@NonNull String placholder, @NonNull GenericPlayer player, @NonNull GenericServer server){
if(luckPerms == null)
return "ERR: LuckPerms not available.";
CacheValue cacheValue = cache.get(player.getUUID());
User user;
if(cacheValue == null || cacheValue.expired()){
// Can cause lag if on main thread.
// But the alternative would be a lot more annoying and error-prone.
user = luckPerms.getUserManager().loadUser(player.getUUID()).join();
cache.put(player.getUUID(), new CacheValue(System.currentTimeMillis(), user));
}else{
user = cacheValue.user();
}
if(user == null)
return "ERR: Player not found.";
String[] parts = placholder.split("\\s+");
if(parts.length == 1)
return "ERR: Missing argument for placeholder " + parts[0] + ".";
return switch(parts[0]){
case "hasGroup" -> String.valueOf(
user.getCachedData().getPermissionData().checkPermission("group." + parts[1])
);
case "hasPermission" -> String.valueOf(
user.getCachedData().getPermissionData().checkPermission(parts[1]).asBoolean()
);
case "primaryGroup" -> user.getPrimaryGroup();
default -> null;
};
}
private record CacheValue(long timestamp, User user){
public boolean expired(){
// Expired if current timestamp minus old one is more than 60k milliseconds (1 minute)
return (timestamp < 0L) || System.currentTimeMillis() - timestamp >= 60_000;
}
}
}
@@ -1,53 +0,0 @@
package ch.andre601.advancedserverlist.luckperms.velocity;
import ch.andre601.advancedserverlist.api.AdvancedServerListAPI;
import ch.andre601.advancedserverlist.api.exceptions.InvalidPlaceholderProviderException;
import ch.andre601.advancedserverlist.luckperms.placeholders.LuckPermsPlaceholder;
import com.google.inject.Inject;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
import com.velocitypowered.api.proxy.ProxyServer;
import net.luckperms.api.LuckPerms;
import net.luckperms.api.LuckPermsProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LuckPermsAddon{
private final ProxyServer proxy;
private final Logger logger = LoggerFactory.getLogger("ASLLuckPermsAddon");
@Inject
public LuckPermsAddon(ProxyServer proxy){
this.proxy = proxy;
}
@Subscribe
public void init(ProxyInitializeEvent event){
if(!proxy.getPluginManager().isLoaded("advancedserverlist")){
logger.warn("AdvancedServerList is not enabled. This plugin is required.");
return;
}
if(!proxy.getPluginManager().isLoaded("luckperms")){
logger.warn("LuckPerms is not enabled. This plugin is required.");
return;
}
LuckPerms luckPerms;
try{
luckPerms = LuckPermsProvider.get();
}catch(IllegalStateException ex){
logger.warn("Unable to obtain LuckPerms API instance. The plugin won't work.");
return;
}
logger.info("Registering LuckPerms Placeholder...");
try{
AdvancedServerListAPI.get().addPlaceholderProvider(new LuckPermsPlaceholder(luckPerms));
logger.info("Successfully registered Placeholder!");
}catch(InvalidPlaceholderProviderException ex){
logger.warn("Unable to register LuckPerms Placeholder! The plugin won't work.");
}
}
}
@@ -1,10 +0,0 @@
name: 'ASLLuckPermsAddon'
author: 'Andre_601'
version: '${plugin.version}'
description: 'LuckPerms Add-on for AdvancedServerList to add placeholders to use.'
softDepends:
- AdvancedServerList
- LuckPerms
main: 'ch.andre601.advancedserverlist.luckperms.bungee.LuckPermsAddon'
@@ -1,16 +0,0 @@
name: 'ASLLuckPermsAddon'
author: 'Andre_601'
version: '${plugin.version}'
main: 'ch.andre601.advancedserverlist.luckperms.paper.LuckPermsAddon'
api-version: '1.19'
dependencies:
server:
AdvancedServerList:
required: false
load: BEFORE
LuckPerms:
required: false
load: BEFORE
@@ -1,20 +0,0 @@
{
"id": "aslluckpermsaddon",
"name": "ASLLuckPermsAddon",
"version": "${plugin.version}",
"description": "LuckPerms Add-on for AdvancedServerList to add placeholders to use.",
"authors": [
"Andre_601"
],
"dependencies": [
{
"id": "advancedserverlist",
"optional": true
},
{
"id": "luckperms",
"optional": true
}
],
"main": "ch.andre601.advancedserverlist.luckperms.velocity.LuckPermsAddon"
}
+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.
+38 -52
View File
@@ -40,9 +40,8 @@
<description>${plugin.description}</description>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>16</maven.compiler.source>
<maven.compiler.target>16</maven.compiler.target>
</properties>
<repositories>
@@ -52,7 +51,7 @@
</repository>
<repository>
<id>repo-sayandevelopment</id>
<url>https://repo.sayandev.org/releases/</url>
<url>https://repo.sayandev.org/snapshots</url>
</repository>
</repositories>
@@ -74,14 +73,14 @@
<dependency>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-api</artifactId>
<version>1.21-R0.3</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.21-R0.3</version>
<version>1.21-R0.1-SNAPSHOT</version>
<type>javadoc</type>
<scope>provided</scope>
</dependency>
@@ -126,19 +125,19 @@
<dependency>
<groupId>com.github.LeonMangler</groupId>
<artifactId>PremiumVanishAPI</artifactId>
<version>2.9.18-2</version>
<version>2.9.0-4</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.sayandev</groupId>
<artifactId>sayanvanish-api</artifactId>
<version>1.7.2</version>
<version>1.6.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.sayandev</groupId>
<artifactId>sayanvanish-proxy-bungeecord</artifactId>
<version>1.7.2</version>
<version>1.6.1</version>
<scope>provided</scope>
</dependency>
@@ -149,16 +148,10 @@
<version>2.0.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.incendo</groupId>
<artifactId>cloud-bungee</artifactId>
<version>${dependency.cloud-bungee}</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<finalName>AdvancedServerList-BungeeCord-${plugin.version}${project.build.build_id}</finalName>
<finalName>AdvancedServerList-BungeeCord-${plugin.version}</finalName>
<resources>
<resource>
<filtering>true</filtering>
@@ -166,51 +159,44 @@
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.1</version>
<version>3.6.0</version>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<relocations>
<relocation>
<pattern>org.bstats</pattern>
<shadedPattern>ch.andre601.advancedserverlist.bungeecord.depends.bstats</shadedPattern>
</relocation>
<relocation>
<pattern>net.byteflux.libby</pattern>
<shadedPattern>ch.andre601.advancedserverlist.bungeecord.depends.libby</shadedPattern>
</relocation>
</relocations>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<relocations>
<relocation>
<pattern>org.bstats</pattern>
<shadedPattern>ch.andre601.advancedserverlist.bungeecord.depends.bstats</shadedPattern>
</relocation>
<relocation>
<pattern>com.alessiodp.libby</pattern>
<shadedPattern>ch.andre601.advancedserverlist.bungeecord.depends.libby</shadedPattern>
</relocation>
<relocation>
<pattern>ch.andre601.expressionparser</pattern>
<shadedPattern>ch.andre601.advancedserverlist.bungeecord.depends.expressionparser</shadedPattern>
</relocation>
<relocation>
<pattern>com.unascribed.flexver</pattern>
<shadedPattern>ch.andre601.advancedserverlist.bungeecord.depends.flexver</shadedPattern>
</relocation>
<relocation>
<pattern>org.incendo.cloud</pattern>
<shadedPattern>ch.andre601.advancedserverlist.bungeecord.depends.cloud</shadedPattern>
</relocation>
</relocations>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
@@ -25,23 +25,23 @@
package ch.andre601.advancedserverlist.bungeecord;
import ch.andre601.advancedserverlist.bungeecord.commands.BungeeCommandManagerInterface;
import ch.andre601.advancedserverlist.bungeecord.listeners.PlayerJoinEventListener;
import ch.andre601.advancedserverlist.bungeecord.listeners.ProxyPingEventListener;
import ch.andre601.advancedserverlist.bungeecord.commands.CmdAdvancedServerList;
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;
import ch.andre601.advancedserverlist.bungeecord.objects.placeholders.BungeeServerPlaceholders;
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.interfaces.core.PluginCore;
import ch.andre601.advancedserverlist.core.interfaces.dialogs.IDialog;
import ch.andre601.advancedserverlist.core.interfaces.dialogs.IDialogList;
import ch.andre601.advancedserverlist.core.profiles.ServerListProfile;
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;
@@ -51,7 +51,6 @@ 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.incendo.cloud.CommandManager;
import org.sayandev.sayanvanish.bungeecord.api.SayanVanishBungeeAPI;
import java.awt.image.BufferedImage;
@@ -65,6 +64,8 @@ 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<>();
@@ -73,7 +74,6 @@ public class BungeeCordCore extends Plugin implements PluginCore<Favicon>{
private AdvancedServerList<Favicon> core;
private FaviconHandler<Favicon> faviconHandler = null;
private BungeeAudiences audiences = null;
private BungeeCommandManagerInterface handler;
private BungeeLibraryManager libraryManager = null;
@@ -98,13 +98,8 @@ public class BungeeCordCore extends Plugin implements PluginCore<Favicon>{
}
this.audiences = BungeeAudiences.create(this);
this.handler = new BungeeCommandManagerInterface(this);
this.core = AdvancedServerList.init(
this,
BungeePlayerPlaceholders.init(),
BungeeServerPlaceholders.init(this),
BungeeProxyPlaceholders.init(this)
);
this.core = AdvancedServerList.init(this,
BungeePlayerPlaceholders.init(), BungeeServerPlaceholders.init(this), BungeeProxyPlaceholders.init(this));
}
@Override
@@ -114,16 +109,23 @@ public class BungeeCordCore extends Plugin implements PluginCore<Favicon>{
audiences.close();
}
@Override
public void loadCommands(){
getProxy().getPluginManager().registerCommand(this, new CmdAdvancedServerList(this));
getPluginLogger().success("Registered <white>/advancedserverlist</white>!");
}
@Override
public void loadEvents(){
new PlayerJoinEventListener(this);
new ProxyPingEventListener(this);
new JoinListener(this);
new PingListener(this);
new PluginMessageListener(this);
}
@Override
public void loadMetrics(){
new Metrics(this, 15585).addCustomChart(new SimplePie("profiles",
() -> String.valueOf(core.fileHandler().profiles().size())
() -> String.valueOf(core.getFileHandler().getProfiles().size())
));
}
@@ -164,22 +166,22 @@ public class BungeeCordCore extends Plugin implements PluginCore<Favicon>{
}
@Override
public AdvancedServerList<Favicon> core(){
public AdvancedServerList<Favicon> getCore(){
return core;
}
@Override
public Path folderPath(){
public Path getFolderPath(){
return getDataFolder().toPath();
}
@Override
public PluginLogger pluginLogger(){
public PluginLogger getPluginLogger(){
return logger;
}
@Override
public FaviconHandler<Favicon> faviconHandler(){
public FaviconHandler<Favicon> getFaviconHandler(){
if(faviconHandler == null)
faviconHandler = new FaviconHandler<>(core);
@@ -187,17 +189,12 @@ public class BungeeCordCore extends Plugin implements PluginCore<Favicon>{
}
@Override
public CommandManager<CmdSender> commandManager(){
return handler.commandHandler();
}
@Override
public String platformInfo(){
public String getPlatformInfo(){
return getProxy().getName() + " " + getProxy().getVersion();
}
@Override
public String loader(){
public String getLoader(){
return "bungeecord";
}
@@ -206,31 +203,16 @@ public class BungeeCordCore extends Plugin implements PluginCore<Favicon>{
return getProxy().getPluginManager().getPlugin(plugin) != null;
}
@Override
public boolean isDialogsSupported(){
return false;
}
@Override
public Favicon createFavicon(BufferedImage image) throws IllegalArgumentException{
return Favicon.create(image);
}
@Override
public IDialogList.IDialogListBuilder createDialogListBuilder(String title){
return null;
}
@Override
public IDialog.IDialogBuilder createDialogBuilder(String title, ServerListProfile profile, int profilesPosition){
return null;
}
public BungeeAudiences audiences(){
public BungeeAudiences getAudiences(){
return audiences;
}
public int onlinePlayers(ServerInfo server){
public int getOnlinePlayers(ServerInfo server){
List<ProxiedPlayer> players = new ArrayList<>(server == null ? getProxy().getPlayers() : server.getPlayers());
// Exclude players when PremiumVanish is enabled and player is hidden.
@@ -244,7 +226,7 @@ public class BungeeCordCore extends Plugin implements PluginCore<Favicon>{
return players.size();
}
public Map<String, ServerPing> fetchedServers(){
public Map<String, ServerPing> getFetchedServers(){
return fetchedServers;
}
@@ -266,11 +248,17 @@ public class BungeeCordCore extends Plugin implements PluginCore<Favicon>{
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());
}));
}
}
@@ -27,10 +27,8 @@ package ch.andre601.advancedserverlist.bungeecord.commands;
import ch.andre601.advancedserverlist.core.interfaces.commands.CmdSender;
import ch.andre601.advancedserverlist.core.parsing.ComponentParser;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.platform.bungeecord.BungeeAudiences;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.connection.ProxiedPlayer;
public class BungeeCmdSender implements CmdSender{
@@ -42,22 +40,13 @@ public class BungeeCmdSender implements CmdSender{
this.bungeeAudiences = bungeeAudiences;
}
@Override
public boolean hasPermission(String permission){
return sender.hasPermission(permission) || sender.hasPermission("advancedserverlist.admin");
}
@Override
public void sendMsg(String msg, Object... args){
bungeeAudiences.sender(sender).sendMessage(ComponentParser.text(String.format(msg, args)).toComponent());
}
@Override
public Audience audience(){
return bungeeAudiences.sender(sender);
}
@Override
public boolean isPlayer(){
return sender instanceof ProxiedPlayer;
}
public CommandSender sender(){
return sender;
}
}
@@ -1,60 +0,0 @@
/*
* 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.bungeecord.commands;
import ch.andre601.advancedserverlist.bungeecord.BungeeCordCore;
import ch.andre601.advancedserverlist.core.interfaces.commands.CmdSender;
import ch.andre601.advancedserverlist.core.interfaces.commands.CommandManagerInterface;
import net.md_5.bungee.api.CommandSender;
import org.incendo.cloud.CommandManager;
import org.incendo.cloud.SenderMapper;
import org.incendo.cloud.bungee.BungeeCommandManager;
import org.incendo.cloud.execution.ExecutionCoordinator;
public class BungeeCommandManagerInterface implements CommandManagerInterface<CommandSender>{
private final BungeeCordCore plugin;
public BungeeCommandManagerInterface(BungeeCordCore plugin){
this.plugin = plugin;
}
@Override
public CommandManager<CmdSender> commandHandler(){
return new BungeeCommandManager<>(
plugin,
ExecutionCoordinator.asyncCoordinator(),
senderMapper()
);
}
@Override
public SenderMapper<CommandSender, CmdSender> senderMapper(){
return SenderMapper.create(
cmdSender -> new BungeeCmdSender(cmdSender, plugin.audiences()),
sender -> ((BungeeCmdSender)sender).sender()
);
}
}
@@ -1,7 +1,7 @@
/*
* MIT License
*
* Copyright (c) 2022-2024 Andre_601
* 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
@@ -23,30 +23,24 @@
*
*/
package ch.andre601.advancedserverlist.core.objects;
package ch.andre601.advancedserverlist.bungeecord.commands;
import java.time.Duration;
import java.util.function.Supplier;
import ch.andre601.advancedserverlist.bungeecord.BungeeCordCore;
import net.kyori.adventure.platform.bungeecord.BungeeAudiences;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.plugin.Command;
public class ValueCache<T>{
public class CmdAdvancedServerList extends Command{
private final Duration duration;
private CacheValue<T> cache = null;
private final BungeeCordCore plugin;
public ValueCache(Duration duration){
this.duration = duration;
public CmdAdvancedServerList(BungeeCordCore plugin){
super("advancedserverlist", "advancedserverlist.command.asl", "asl");
this.plugin = plugin;
}
public T get(Supplier<T> supplier){
if(cache == null || cache.expired())
cache = new CacheValue<>(System.currentTimeMillis(), duration, supplier.get());
return cache.value();
}
private record CacheValue<T>(long timestamp, Duration duration, T value){
public boolean expired(){
return (timestamp < 0L) || System.currentTimeMillis() - timestamp >= duration.toMillis();
}
@Override
public void execute(CommandSender sender, String[] args){
plugin.getCore().getCommandHandler().handle(new BungeeCmdSender(sender, plugin.getAudiences()), args);
}
}
@@ -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;
@@ -75,17 +76,17 @@ public class BungeeEventWrapper implements GenericEventWrapper<Favicon, BungeePl
}
@Override
public void maxPlayers(int maxPlayers){
public void setMaxPlayers(int maxPlayers){
ping.getPlayers().setMax(maxPlayers);
}
@Override
public void onlinePlayers(int onlinePlayers){
public void setOnlinePlayers(int onlinePlayers){
ping.getPlayers().setOnline(onlinePlayers);
}
@Override
public void motd(Component component){
public void setMotd(Component component){
ping.setDescriptionComponent(new TextComponent(BungeeComponentSerializer.get().serialize(component)));
}
@@ -95,13 +96,13 @@ public class BungeeEventWrapper implements GenericEventWrapper<Favicon, BungeePl
}
@Override
public void playerCount(String name){
public void setPlayerCount(String name){
protocol.setName(name);
protocol.setProtocol(-1);
}
@Override
public void players(List<String> lines, BungeePlayerImpl player, GenericServer server){
public void setPlayers(List<String> lines, BungeePlayerImpl player, GenericServer server){
ServerPing.PlayerInfo[] players = new ServerPing.PlayerInfo[lines.size()];
for(int i = 0; i < players.length; i++){
@@ -119,17 +120,17 @@ public class BungeeEventWrapper implements GenericEventWrapper<Favicon, BungeePl
}
@Override
public void playersHidden(){
public void setPlayersHidden(){
ping.getPlayers().setSample(new ServerPing.PlayerInfo[0]);
}
@Override
public void favicon(Favicon favicon){
public void setFavicon(Favicon favicon){
ping.setFavicon(favicon);
}
@Override
public void defaultFavicon(){
public void setDefaultFavicon(){
ping.setFavicon(ping.getFaviconObject());
}
@@ -153,55 +154,55 @@ public class BungeeEventWrapper implements GenericEventWrapper<Favicon, BungeePl
}
@Override
public int protocolVersion(){
public int getProtocolVersion(){
return event.getConnection().getVersion();
}
@Override
public int onlinePlayers(){
return plugin.onlinePlayers(null);
public int getOnlinePlayers(){
return plugin.getOnlinePlayers(null);
}
@Override
public int maxPlayers(){
public int getMaxPlayers(){
return ping.getPlayers().getMax();
}
@Override
public String playerIP(){
public String getPlayerIP(){
return ((InetSocketAddress)event.getConnection().getSocketAddress()).getHostString();
}
@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
public String virtualHost(){
public String getVirtualHost(){
return this.resolveHost(event.getConnection().getVirtualHost());
}
@Override
public PluginCore<Favicon> plugin(){
public PluginCore<Favicon> getPlugin(){
return plugin;
}
@@ -211,7 +212,7 @@ public class BungeeEventWrapper implements GenericEventWrapper<Favicon, BungeePl
}
@Override
public GenericServer createServer(int playersOnline, int playersMax, String host){
public GenericServer createGenericServer(int playersOnline, int playersMax, String host){
Map<String, ServerInfo> servers = plugin.getProxy().getServers();
return new BungeeProxyImpl(servers, playersOnline, playersMax, host);
@@ -34,10 +34,10 @@ import net.md_5.bungee.event.EventHandler;
import java.net.InetSocketAddress;
public class PlayerJoinEventListener implements Listener{
public class JoinListener implements Listener{
private final BungeeCordCore plugin;
public PlayerJoinEventListener(BungeeCordCore plugin){
public JoinListener(BungeeCordCore plugin){
this.plugin = plugin;
plugin.getProxy().getPluginManager().registerListener(plugin, this);
}
@@ -47,15 +47,15 @@ public class PlayerJoinEventListener implements Listener{
InetSocketAddress address = (InetSocketAddress)event.getPlayer().getPendingConnection().getSocketAddress();
ProxiedPlayer player = event.getPlayer();
plugin.core().playerHandler().addPlayer(address.getHostString(), player.getName(), player.getUniqueId());
plugin.getCore().getPlayerHandler().addPlayer(address.getHostString(), player.getName(), player.getUniqueId());
if(player.hasPermission("advancedserverlist.admin") || player.hasPermission("advancedserverlist.updatecheck")){
if(plugin.audiences() == null || plugin.core().updateChecker() == null)
if(plugin.getAudiences() == null || plugin.getCore().getUpdateChecker() == null)
return;
BungeeCmdSender sender = new BungeeCmdSender(player, plugin.audiences());
BungeeCmdSender sender = new BungeeCmdSender(player, plugin.getAudiences());
plugin.core().updateChecker().performCachedUpdateCheck(sender);
plugin.getCore().getUpdateChecker().performCachedUpdateCheck(sender);
}
}
}
@@ -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 ProxyPingEventListener implements Listener{
public class PingListener implements Listener{
private final BungeeCordCore plugin;
public ProxyPingEventListener(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,16 +27,24 @@ package ch.andre601.advancedserverlist.bungeecord.objects.impl;
import ch.andre601.advancedserverlist.api.bungeecord.objects.BungeePlayer;
import ch.andre601.advancedserverlist.core.objects.CachedPlayer;
import org.jspecify.annotations.NullMarked;
import java.util.UUID;
@NullMarked
public record BungeePlayerImpl(CachedPlayer player, int protocol) implements BungeePlayer{
public class BungeePlayerImpl implements BungeePlayer{
private final String name;
private final int protocol;
private final UUID uuid;
public BungeePlayerImpl(CachedPlayer player, int protocol){
this.name = player.name();
this.protocol = protocol;
this.uuid = player.uuid();
}
@Override
public String getName(){
return player.name();
return name;
}
@Override
@@ -46,6 +54,6 @@ public record BungeePlayerImpl(CachedPlayer player, int protocol) implements Bun
@Override
public UUID getUUID(){
return player.uuid();
return uuid;
}
}
@@ -27,13 +27,12 @@ package ch.andre601.advancedserverlist.bungeecord.objects.impl;
import ch.andre601.advancedserverlist.api.bungeecord.objects.BungeeProxy;
import net.md_5.bungee.api.config.ServerInfo;
import org.jspecify.annotations.NonNull;
import java.util.Map;
public record BungeeProxyImpl(Map<String, ServerInfo> servers, int playersOnline, int playersMax, String host) implements BungeeProxy{
@Override
public @NonNull Map<String, ServerInfo> getServers(){
public Map<String, ServerInfo> getServers(){
return servers;
}
@@ -28,12 +28,9 @@ package ch.andre601.advancedserverlist.bungeecord.objects.placeholders;
import ch.andre601.advancedserverlist.api.PlaceholderProvider;
import ch.andre601.advancedserverlist.api.objects.GenericPlayer;
import ch.andre601.advancedserverlist.api.objects.GenericServer;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.util.Locale;
@NullMarked
public class BungeePlayerPlaceholders extends PlaceholderProvider{
private BungeePlayerPlaceholders(){
@@ -45,7 +42,7 @@ public class BungeePlayerPlaceholders extends PlaceholderProvider{
}
@Override
public @Nullable String parsePlaceholder(String placeholder, GenericPlayer player, GenericServer server){
public String parsePlaceholder(String placeholder, GenericPlayer player, GenericServer server){
return switch(placeholder.toLowerCase(Locale.ROOT)){
case "name" -> player.getName();
case "protocol" -> String.valueOf(player.getProtocol());
@@ -9,10 +9,7 @@ import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer;
import net.md_5.bungee.api.ServerPing;
import net.md_5.bungee.api.chat.BaseComponent;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public class BungeeProxyPlaceholders extends PlaceholderProvider{
private final BungeeCordCore plugin;
@@ -28,12 +25,12 @@ public class BungeeProxyPlaceholders extends PlaceholderProvider{
}
@Override
public @Nullable String parsePlaceholder(String placeholder, GenericPlayer player, GenericServer server){
public String parsePlaceholder(String placeholder, GenericPlayer player, GenericServer server){
String[] args = placeholder.split("\\s", 2);
if(args.length < 2)
return null;
ServerPing ping = plugin.fetchedServers().get(args[1]);
ServerPing ping = plugin.getFetchedServers().get(args[1]);
return switch(args[0]){
case "status" -> ping == null ? "offline" : "online";
@@ -31,12 +31,9 @@ import ch.andre601.advancedserverlist.api.objects.GenericPlayer;
import ch.andre601.advancedserverlist.api.objects.GenericServer;
import ch.andre601.advancedserverlist.bungeecord.BungeeCordCore;
import net.md_5.bungee.api.config.ServerInfo;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.net.InetSocketAddress;
@NullMarked
public class BungeeServerPlaceholders extends PlaceholderProvider{
private final BungeeCordCore plugin;
@@ -51,7 +48,7 @@ public class BungeeServerPlaceholders extends PlaceholderProvider{
}
@Override
public @Nullable String parsePlaceholder(String placeholder, GenericPlayer player, GenericServer server){
public String parsePlaceholder(String placeholder, GenericPlayer player, GenericServer server){
if(!(server instanceof BungeeProxy proxy))
return null;
@@ -71,7 +68,7 @@ public class BungeeServerPlaceholders extends PlaceholderProvider{
if(info == null)
continue;
players += plugin.onlinePlayers(info);
players += plugin.getOnlinePlayers(info);
}
yield String.valueOf(players);
@@ -90,7 +87,7 @@ public class BungeeServerPlaceholders extends PlaceholderProvider{
if(args.length > 2)
yield "";
if(args.length == 2 && !args[1].isEmpty()){
if(args.length == 2 && args[1] != null && !args[1].isEmpty()){
ServerInfo info = proxy.getServers().get(args[1]);
if(info == null)
yield "";
+45 -49
View File
@@ -39,11 +39,9 @@
<version>${plugin.version}</version>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<plugin.config.version>10</plugin.config.version>
<maven.compiler.source>16</maven.compiler.source>
<maven.compiler.target>16</maven.compiler.target>
<plugin.config.version>8</plugin.config.version>
</properties>
<repositories>
@@ -79,7 +77,7 @@
<dependency>
<groupId>ch.andre601</groupId>
<artifactId>expressionparser</artifactId>
<version>1.6.1</version>
<version>1.5.0</version>
<scope>compile</scope>
</dependency>
@@ -93,7 +91,7 @@
<dependency>
<groupId>org.spongepowered</groupId>
<artifactId>configurate-hocon</artifactId>
<version>4.2.0</version>
<version>4.1.2</version>
<scope>provided</scope>
</dependency>
@@ -121,7 +119,7 @@
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.13.2</version>
<version>2.11.0</version>
<scope>provided</scope>
</dependency>
<dependency>
@@ -133,42 +131,15 @@
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.5.0-jre</version>
<version>33.4.0-jre</version>
<scope>provided</scope>
</dependency>
<!-- Command Framework -->
<dependency>
<groupId>org.incendo</groupId>
<artifactId>cloud-annotations</artifactId>
<version>${dependency.cloud-annotations}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.incendo</groupId>
<artifactId>cloud-minecraft-extras</artifactId>
<version>${dependency.cloud-minecraft-extras}</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<groupId>net.kyori</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Annotations -->
<dependency>
<groupId>org.jspecify</groupId>
<artifactId>jspecify</artifactId>
<version>1.0.0</version>
</dependency>
<!-- Plugins -->
<dependency>
<groupId>net.william278</groupId>
<artifactId>papiproxybridge</artifactId>
<version>1.8.4</version>
<version>1.7.2</version>
<scope>provided</scope>
</dependency>
<dependency>
@@ -183,24 +154,18 @@
<version>3.5.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.github.miniplaceholders</groupId>
<artifactId>miniplaceholders-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<!-- Test dependencies -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.14.1</version>
<version>5.11.4</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>AdvancedServerList-Core-${plugin.version}${project.build.build_id}</finalName>
<finalName>AdvancedServerList-Core-${plugin.version}</finalName>
<resources>
<resource>
<filtering>true</filtering>
@@ -221,12 +186,43 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.14.1</version>
<version>3.13.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
<createDependencyReducedPom>false</createDependencyReducedPom>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
<relocations>
<relocation>
<pattern>com.unascribed</pattern>
<shadedPattern>ch.andre601.advancedserverlist.core.depends.flexver</shadedPattern>
</relocation>
<relocation>
<pattern>ch.andre601.expressionparser</pattern>
<shadedPattern>ch.andre601.advancedserverlist.core.depends.expressionparser</shadedPattern>
</relocation>
</relocations>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
@@ -29,48 +29,24 @@ import ch.andre601.advancedserverlist.api.AdvancedServerListAPI;
import ch.andre601.advancedserverlist.api.PlaceholderProvider;
import ch.andre601.advancedserverlist.core.check.UpdateChecker;
import ch.andre601.advancedserverlist.core.commands.CommandHandler;
import ch.andre601.advancedserverlist.core.commands.CommandTypePostprocessor;
import ch.andre601.advancedserverlist.core.compat.maintenance.MaintenancePlaceholder;
import ch.andre601.advancedserverlist.core.file.FileHandler;
import ch.andre601.advancedserverlist.core.interfaces.commands.CmdSender;
import ch.andre601.advancedserverlist.core.interfaces.commands.CommandType;
import ch.andre601.advancedserverlist.core.interfaces.core.PluginCore;
import ch.andre601.advancedserverlist.core.parsing.TextCenterUtil;
import ch.andre601.advancedserverlist.core.placeholders.DatePlaceholder;
import ch.andre601.advancedserverlist.core.profiles.ServerListProfile;
import ch.andre601.advancedserverlist.core.profiles.conditions.ProfileConditionParser;
import ch.andre601.advancedserverlist.core.profiles.handlers.PlayerHandler;
import org.incendo.cloud.CommandManager;
import org.incendo.cloud.annotations.AnnotationParser;
import org.incendo.cloud.description.Description;
import org.incendo.cloud.injection.ParameterInjector;
import org.incendo.cloud.parser.ParserDescriptor;
import org.incendo.cloud.parser.standard.BooleanParser;
import org.incendo.cloud.parser.standard.IntegerParser;
import org.incendo.cloud.parser.standard.StringParser;
import org.incendo.cloud.permission.Permission;
import org.incendo.cloud.suggestion.Suggestion;
import org.incendo.cloud.suggestion.SuggestionProvider;
import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
public class AdvancedServerList<F>{
public static <F> AdvancedServerList<F> init(PluginCore<F> plugin, PlaceholderProvider... placeholders){
List<PlaceholderProvider> providers = new ArrayList<>(Arrays.asList(placeholders));
providers.add(new DatePlaceholder());
return new AdvancedServerList<>(plugin, providers);
}
private final PluginCore<F> plugin;
private final FileHandler fileHandler;
private final CommandHandler commandHandler;
private final PlayerHandler playerHandler;
private final TextCenterUtil textCenterUtil;
@@ -84,10 +60,11 @@ public class AdvancedServerList<F>{
private AdvancedServerList(PluginCore<F> plugin, List<PlaceholderProvider> placeholders){
this.plugin = plugin;
this.fileHandler = new FileHandler(this);
this.commandHandler = new CommandHandler(this);
this.playerHandler = new PlayerHandler(this);
this.textCenterUtil = new TextCenterUtil(this);
plugin.pluginLogger().info("Registering internal Placeholders...");
plugin.getPluginLogger().info("Registering internal Placeholders...");
placeholders.forEach(this::addPlaceholder);
@@ -96,52 +73,60 @@ public class AdvancedServerList<F>{
}
resolveVersion();
plugin().pluginLogger().info("Starting <white>AdvancedServerList v%s</white>...", version);
getPlugin().getPluginLogger().info("Starting <white>AdvancedServerList v%s</white>...", version);
load();
}
public static AdvancedServerListAPI api(){
public static <F> AdvancedServerList<F> init(PluginCore<F> plugin, PlaceholderProvider... placeholders){
return new AdvancedServerList<>(plugin, Arrays.asList(placeholders));
}
public static AdvancedServerListAPI getApi(){
return api;
}
public PluginCore<F> plugin(){
public PluginCore<F> getPlugin(){
return plugin;
}
public FileHandler fileHandler(){
public FileHandler getFileHandler(){
return fileHandler;
}
public PlayerHandler playerHandler(){
public CommandHandler getCommandHandler(){
return commandHandler;
}
public PlayerHandler getPlayerHandler(){
return playerHandler;
}
public String version(){
public String getVersion(){
return version;
}
public ProfileConditionParser parser(){
public ProfileConditionParser getParser(){
return parser;
}
public UpdateChecker updateChecker(){
public UpdateChecker getUpdateChecker(){
return updateChecker;
}
public TextCenterUtil textCenterUtil(){
public TextCenterUtil getTextCenterUtil(){
return textCenterUtil;
}
public void disable(){
plugin().pluginLogger().info("[-] Saving <white>playercache.json</white> file...");
playerHandler().save();
getPlugin().getPluginLogger().info("[-] Saving <white>playercache.json</white> file...");
getPlayerHandler().save();
if(updateChecker != null){
plugin().pluginLogger().info("[-] Disabling Update Checker...");
getPlugin().getPluginLogger().info("[-] Disabling Update Checker...");
updateChecker.disable();
}
plugin().pluginLogger().success("AdvancedServerList disabled!");
getPlugin().getPluginLogger().success("AdvancedServerList disabled!");
}
public void clearFaviconCache(){
@@ -149,69 +134,68 @@ public class AdvancedServerList<F>{
}
public void clearPlayerCache(){
playerHandler().clearCache();
getPlayerHandler().clearCache();
}
private void load(){
if(!fileHandler().loadConfig()){
plugin().pluginLogger().failure("Unable to load <white>config.yml</white>! 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(fileHandler().isOldConfig()){
plugin().pluginLogger().info("[-] Detected old <white>config.yml</white>. Attempting to migrate...");
if(fileHandler().migrateConfig()){
plugin().pluginLogger().success("Migration completed successfully!");
if(getFileHandler().isOldConfig()){
getPlugin().getPluginLogger().info("[-] Detected old <white>config.yml</white>. Attempting to migrate...");
if(getFileHandler().migrateConfig()){
getPlugin().getPluginLogger().success("Migration completed successfully!");
}else{
plugin().pluginLogger().failure("Couldn't migrate <white>config.yml</white>! Check previous lines for errors.");
getPlugin().getPluginLogger().failure("Couldn't migrate <white>config.yml</white>! Check previous lines for errors.");
return;
}
}
if(fileHandler().getBool(true, "printBanner")){
if(getFileHandler().getBool(true, "printBanner")){
printBanner();
}
plugin().pluginLogger().info("Platform: <white>%s</white>", plugin.platformInfo());
plugin().pluginLogger().info("");
getPlugin().getPluginLogger().info("Platform: <white>%s</white>", plugin.getPlatformInfo());
getPlugin().getPluginLogger().info("");
if(fileHandler().loadProfiles()){
plugin().pluginLogger().success("Successfully loaded <white>%d</white> profile(s)!", fileHandler().profiles().size());
if(getFileHandler().loadProfiles()){
getPlugin().getPluginLogger().success("Successfully loaded <white>%d</white> profile(s)!", getFileHandler().getProfiles().size());
}else{
plugin().pluginLogger().failure("Unable to load profiles! Check previous lines for errors.");
getPlugin().getPluginLogger().failure("Unable to load profiles! Check previous lines for errors.");
return;
}
loadCommands();
getPlugin().loadFaviconHandler(this);
plugin.loadCommands();
plugin.loadEvents();
playerHandler().load();
getPlayerHandler().load();
plugin.loadMetrics();
plugin.startScheduler();
plugin().pluginLogger().success("<green>AdvancedServerList is ready!");
getPlugin().getPluginLogger().success("<green>AdvancedServerList is ready!");
if(fileHandler().getBoolean("checkUpdates"))
if(getFileHandler().getBoolean("checkUpdates"))
this.updateChecker = new UpdateChecker(this);
}
private void printBanner(){
plugin().pluginLogger().info("<#3b90ff> __ _______ ___");
plugin().pluginLogger().info("<#3b90ff> /\\ \\ / ____|_| | |");
plugin().pluginLogger().info("<#3b90ff> / \\ \\ | (_(___ | | |");
plugin().pluginLogger().info("<#3b90ff> / /\\ \\ \\ \\___ \\ \\| | |");
plugin().pluginLogger().info("<#3b90ff> / ____ \\ \\ ____) | | |_|____");
plugin().pluginLogger().info("<#3b90ff>/_/_/ \\_\\_\\_____/_/|______|_|");
plugin().pluginLogger().info("");
seasonalText();
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("");
}
private void resolveVersion(){
try(InputStream stream = getClass().getResourceAsStream("/version.properties")){
try(InputStream is = getClass().getResourceAsStream("/version.properties")){
Properties properties = new Properties();
properties.load(stream);
properties.load(is);
version = properties.getProperty("version");
}catch(IOException ex){
@@ -220,118 +204,6 @@ public class AdvancedServerList<F>{
}
private void addPlaceholder(PlaceholderProvider placeholderProvider){
AdvancedServerList.api().addPlaceholderProvider(placeholderProvider);
}
private void loadCommands(){
plugin().loadFaviconHandler(this);
CommandManager<CmdSender> commandManager = plugin().commandManager();
commandManager.parameterInjectorRegistry().registerInjector(AdvancedServerList.class, ParameterInjector.constantInjector(this));
commandManager.registerCommandPostProcessor(new CommandTypePostprocessor());
AnnotationParser<CmdSender> annotationParser = new AnnotationParser<>(commandManager, CmdSender.class);
annotationParser.registerBuilderModifier(
CommandType.class,
(annotation, builder) -> builder.meta(CommandHandler.COMMAND_TYPE, annotation.value())
);
annotationParser.parse(new CommandHandler(commandManager));
for(String option : CommandHandler.PROFILE_OPTIONS){
commandManager.command(
commandManager.commandBuilder("advancedserverlist", "asl")
.literal("profiles")
.literal("set")
.required(
"profile",
StringParser.stringParser(),
Description.of("The profile to edit."),
SuggestionProvider.suggesting(
fileHandler().profiles().stream()
.map(ServerListProfile::file)
.map(Suggestion::suggestion)
.toList()
)
)
.literal(option)
.optional("value", descriptor(option), description(option))
.commandDescription(Description.of("Sets or resets profile values."))
.handler(context -> CommandHandler.handleSet(this, context))
.meta(CommandHandler.OPTION, option)
.permission(
Permission.anyOf(
Permission.permission("advancedserverlist.admin"),
Permission.permission("advancedserverlist.command.profiles"),
Permission.permission("advancedserverlist.command.profiles.set")
)
)
);
}
plugin().pluginLogger().success("Loaded Command <white>/advancedserverlist</white>!");
}
private ParserDescriptor<CmdSender, ?> descriptor(String option){
return switch(option){
case "playercount.hideplayers", "playercount.hideplayershover", "playercount.extraplayers.enabled",
"playercount.maxplayers.enabled", "playercount.onlineplayers.enabled" -> BooleanParser.booleanParser();
case "priority" -> IntegerParser.integerParser();
default -> StringParser.greedyStringParser();
};
}
private Description description(String option){
return switch(option){
case "playercount.hideplayers", "playercount.hideplayershover", "playercount.extraplayers.enabled",
"playercount.maxplayers.enabled", "playercount.onlineplayers.enabled" -> Description.of(
"Whether this option should be enabled or not. Leave empty to unset it."
);
case "priority" -> Description.of("Priority this Profile should have. Leave empty to reset.");
case "motd", "playercount.hover" -> Description.of(
"Lines to set. Use \\n or <newline> to set a linebreak. Leave empty to reset."
);
default -> Description.of("The Value to use for this option. Leave empty to reset.");
};
}
// Adding some seasonal messages
private void seasonalText(){
LocalDate date = LocalDate.now();
switch(date.getMonth()){
case JANUARY -> {
if(date.getDayOfMonth() == 1){
plugin().pluginLogger().info("Happy new Year!");
plugin().pluginLogger().info("");
}
}
case JUNE -> {
plugin().pluginLogger().info("Happy Pride Month!");
plugin().pluginLogger().info("");
}
case AUGUST -> {
if(date.getDayOfMonth() == 1){
plugin().pluginLogger().info("Happy Birthday Switzerland!");
plugin().pluginLogger().info("");
}
}
case OCTOBER -> {
if(date.getDayOfMonth() == 31){
plugin().pluginLogger().info("Happy Halloween!");
plugin().pluginLogger().info("");
}
}
case DECEMBER -> {
if(date.getDayOfMonth() == 13){
plugin().pluginLogger().info("Happy Birthday Andre_601!");
plugin().pluginLogger().info("");
}else
if(date.getDayOfMonth() > 23 && date.getDayOfMonth() < 27){
plugin().pluginLogger().info("Merry Christmas!");
plugin().pluginLogger().info("");
}
}
}
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;
@@ -68,8 +68,8 @@ public class UpdateChecker{
public UpdateChecker(AdvancedServerList<?> core){
this.core = core;
this.logger = core.plugin().pluginLogger();
this.loader = core.plugin().loader();
this.logger = core.getPlugin().getPluginLogger();
this.loader = core.getPlugin().getLoader();
this.client = HttpClient.newBuilder().executor(Executors.newSingleThreadExecutor(new UpdateCheckThread())).build();
startUpdateChecker();
@@ -88,10 +88,10 @@ public class UpdateChecker{
if(version == null)
return;
int result = version.compare(core.version());
int result = version.compare(core.getVersion());
if(result == -1){
sender.sendPrefixedMsg("<green>A new Version of AdvancedServerList is available!");
sender.sendPrefixedMsg("<green>Your version: <white>%s", core.version());
sender.sendPrefixedMsg("<green>Your version: <white>%s", core.getVersion());
sender.sendPrefixedMsg("<green>Version on Modrinth: <white>%s", version.versionNumber());
sender.sendPrefixedMsg(
"<click:open_url:'https://modrinth.com/plugin/advancedserverlist/version/%s'>" +
@@ -101,12 +101,8 @@ public class UpdateChecker{
);
}else
if(result == 1){
sender.sendPrefixedMsg(
"Your version (<white>%</white>) is newer than what is available on Modrinth (<white>%s</white>).",
core.version(),
version.versionNumber()
);
sender.sendPrefixedMsg("Are you using a Development Version?");
sender.sendPrefixedMsg("You seem to run a newer Version (<white>%</white>) than what is available on Modrinth (<white>%s</white>).", core.getVersion(), version.versionNumber());
sender.sendPrefixedMsg("Are you running a development build?");
}
});
}
@@ -137,18 +133,14 @@ public class UpdateChecker{
logger.debug(UpdateChecker.class, "Comparing versions...");
int result = version.compare(core.version());
int result = version.compare(core.getVersion());
switch(result){
case -1 -> printUpdateBanner(version);
case 0 -> logger.success("You are running the latest version!");
case 1 -> {
logger.info(
"[?] Your version (<white>%s</white>) is newer than what is available on Modrint (<white>%s</white>).",
core.version(),
version.versionNumber()
);
logger.info(" Are you using a Development Version?");
}
case 1 -> logger.info(
"[?] 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()
);
}
});
}, 0L, 12L, TimeUnit.HOURS);
@@ -163,7 +155,7 @@ public class UpdateChecker{
logger.debug(UpdateChecker.class, "Checking '%s' for updates...", finalUrl);
if(core.version().equals("UNKNOWN")){
if(core.getVersion().equals("UNKNOWN")){
logger.warn("Cannot perform update check. Plugin version is 'UNKNOWN'!");
return CompletableFuture.completedFuture(null);
}
@@ -173,7 +165,7 @@ public class UpdateChecker{
request = HttpRequest.newBuilder()
.uri(new URI(finalUrl))
.timeout(Duration.ofSeconds(5))
.header("User-Agent", "AdvancedServerList-" + loader + "/" + core.version())
.header("User-Agent", "AdvancedServerList-" + loader + "/" + core.getVersion())
.build();
}catch(URISyntaxException ex){
logger.warn("Cannot perform update check. URL '%s' is not a valid URI.", ex, finalUrl);
@@ -193,7 +185,7 @@ public class UpdateChecker{
logger.warn("=======================================================================");
logger.warn("You are running an outdated version of AdvancedServerList!");
logger.warn("");
logger.warn("Your version: <white>%s</white>", core.version());
logger.warn("Your version: <white>%s</white>", core.getVersion());
logger.warn("Modrinth: <white>%s</white>", version.versionNumber());
logger.warn("");
File diff suppressed because it is too large. Load diff
@@ -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,22 +0,0 @@
package ch.andre601.advancedserverlist.core.commands;
import ch.andre601.advancedserverlist.core.interfaces.commands.CmdSender;
import ch.andre601.advancedserverlist.core.interfaces.commands.CommandType;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.incendo.cloud.execution.postprocessor.CommandPostprocessingContext;
import org.incendo.cloud.execution.postprocessor.CommandPostprocessor;
import org.incendo.cloud.services.type.ConsumerService;
public class CommandTypePostprocessor implements CommandPostprocessor<CmdSender>{
@Override
public void accept(@NonNull CommandPostprocessingContext<CmdSender> context){
CmdSender sender = context.commandContext().sender();
CommandType.Type type = context.commandContext().command().commandMeta().get(CommandHandler.COMMAND_TYPE);
if(type == CommandType.Type.ALL || (type == CommandType.Type.PLAYER_ONLY && sender.isPlayer()))
return;
sender.sendErrorMsg("<red>This command can only be executed by players!");
ConsumerService.interrupt();
}
}
@@ -1,172 +0,0 @@
/*
* 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 ch.andre601.advancedserverlist.api.objects.NullBool;
import ch.andre601.advancedserverlist.core.interfaces.commands.CmdSender;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public class ProfileOptionsSender{
private final List<OptionStringBuilder> builders = new ArrayList<>();
private final String file;
public ProfileOptionsSender(String file){
this.file = file;
}
public ProfileOptionsSender append(OptionStringBuilder builder){
this.builders.add(builder.file(file));
return this;
}
public void send(CmdSender sender){
sender.sendMsg();
sender.sendPrefixedMsg("Profile Info [<white>%s</white>]", file);
for(OptionStringBuilder builder : builders){
sender.sendMsg(builder.build());
}
sender.sendMsg();
sender.sendMsg("[<grey>Hover for info, click for command</grey>]");
sender.sendMsg();
}
public static class OptionStringBuilder{
public static OptionStringBuilder of(String displayName, String hover){
return new OptionStringBuilder(displayName)
.hover(hover == null || hover.isEmpty() ? "<i>Not set</i>" : hover)
.color(hover == null || hover.isEmpty() ? ColorName.RED : ColorName.GREEN);
}
public static OptionStringBuilder of(String displayName, List<String> hover){
return new OptionStringBuilder(displayName)
.hover(hover == null || hover.isEmpty() ? "<i>Not set</i>" : String.join("\n<reset>", hover))
.color(hover == null || hover.isEmpty() ? ColorName.RED : ColorName.GREEN);
}
public static OptionStringBuilder of(String displayName, NullBool hover){
return new OptionStringBuilder(displayName)
.hover(hover == null || !hover.getOrDefault(false) ? "<red>Disabled</red>" : "<green>Enabled</green>")
.color(hover == null || !hover.getOrDefault(false) ? ColorName.RED : ColorName.GREEN);
}
public static OptionStringBuilder of(String displayName, int hover){
return new OptionStringBuilder(displayName)
.hover(String.valueOf(hover));
}
public static OptionStringBuilder of(String displayName){
return new OptionStringBuilder(displayName).hover(null);
}
private final String displayName;
private String file = null;
private String prefix = "├─";
private ColorName color = ColorName.AQUA;
private String hover = "<i>Not set</i>";
private String option = null;
private OptionStringBuilder(String displayName){
this.displayName = displayName;
}
public OptionStringBuilder file(String file){
this.file = file;
return this;
}
public OptionStringBuilder prefix(String prefix){
this.prefix = prefix;
return this;
}
public OptionStringBuilder color(ColorName color){
this.color = color;
return this;
}
public OptionStringBuilder hover(String hover){
this.hover = hover;
return this;
}
public OptionStringBuilder option(String option){
this.option = option;
return this;
}
public String build(){
StringBuilder builder = new StringBuilder("<grey>")
.append(prefix)
.append(" <white>[")
.append('<')
.append(color)
.append('>');
if(hover != null && !hover.isEmpty()){
builder.append("<hover:show_text:\"")
.append(hover)
.append("\">");
}
if(option != null && !option.isEmpty() && file != null && !file.isEmpty()){
builder.append("<click:suggest_command:/asl profiles set ")
.append(file)
.append(" ")
.append(option)
.append(" >");
}
builder.append(displayName);
if(option != null && !option.isEmpty() && file != null && !file.isEmpty())
builder.append("</click>");
if(hover != null && !hover.isEmpty())
builder.append("</hover>");
builder.append("</")
.append(color)
.append(">]</white></grey>");
return builder.toString();
}
}
public enum ColorName{
RED,
GREEN,
AQUA;
@Override
public String toString(){
return this.name().toLowerCase(Locale.ROOT);
}
}
}
@@ -4,10 +4,7 @@ import ch.andre601.advancedserverlist.api.PlaceholderProvider;
import ch.andre601.advancedserverlist.api.objects.GenericPlayer;
import ch.andre601.advancedserverlist.api.objects.GenericServer;
import ch.andre601.advancedserverlist.core.AdvancedServerList;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public class MaintenancePlaceholder extends PlaceholderProvider{
private final AdvancedServerList<?> plugin;
@@ -18,8 +15,8 @@ public class MaintenancePlaceholder extends PlaceholderProvider{
this.plugin = plugin;
}
@Override
public @Nullable String parsePlaceholder(String placeholder, GenericPlayer player, GenericServer server){
if(!placeholder.equals("maintenanceEnabled") || !plugin.plugin().isPluginEnabled("Maintenance"))
public String parsePlaceholder(String placeholder, GenericPlayer player, GenericServer server){
if(!placeholder.equals("maintenanceEnabled") || !plugin.getPlugin().isPluginEnabled("Maintenance"))
return null;
return String.valueOf(MaintenanceUtil.get().isMaintenanceEnabled());
@@ -1,11 +0,0 @@
package ch.andre601.advancedserverlist.core.compat.miniplaceholders;
import io.github.miniplaceholders.api.MiniPlaceholders;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
public class MiniPlaceholdersUtil{
public static TagResolver tagResolvers(){
return MiniPlaceholders.globalPlaceholders();
}
}
@@ -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).getFirst();
}
public <P> P getPlayer(Collection<P> players){
if(players.isEmpty())
return null;
return List.copyOf(players).getFirst();
}
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;
}
}
}
@@ -44,28 +44,28 @@ import java.util.List;
public class PingEventHandler{
public static <F, P extends GenericPlayer> ProfileEntry handleEvent(GenericEventWrapper<F, P> event){
event.plugin().pluginLogger().debug(PingEventHandler.class, "Received ping event. Handling it...");
event.getPlugin().getPluginLogger().debug(PingEventHandler.class, "Received ping event. Handling it...");
if(event.isInvalidProtocol()){
event.plugin().pluginLogger().debug(PingEventHandler.class, "Not handling event. Protocol was invalid.");
event.getPlugin().getPluginLogger().debug(PingEventHandler.class, "Not handling event. Protocol was invalid.");
return null;
}
PluginCore<F> plugin = event.plugin();
String host = event.virtualHost();
PluginLogger logger = plugin.pluginLogger();
PluginCore<F> plugin = event.getPlugin();
String host = event.getVirtualHost();
PluginLogger logger = plugin.getPluginLogger();
logger.debug(PingEventHandler.class, "Protocol valid. Continue handling...");
int online = event.onlinePlayers();
int max = event.maxPlayers();
int online = event.getOnlinePlayers();
int max = event.getMaxPlayers();
P player = event.createPlayer(
plugin.core().playerHandler().getCachedPlayer(event.playerIP()),
event.protocolVersion()
plugin.getCore().getPlayerHandler().getCachedPlayer(event.getPlayerIP()),
event.getProtocolVersion()
);
GenericServer server = event.createServer(online, max, host);
GenericServer server = event.createGenericServer(online, max, host);
ServerListProfile profile = ProfileManager.resolveProfile(plugin.core(), player, server);
ServerListProfile profile = ProfileManager.resolveProfile(plugin.getCore(), player, server);
if(profile == null){
logger.debugWarn(PingEventHandler.class, "Server List Profile couldn't be resolved properly. Cancelling event handling...");
@@ -93,12 +93,11 @@ public class PingEventHandler{
String onlinePlayers = ComponentParser.text(entry.onlinePlayersCount())
.modifyText(text -> StringReplacer.replace(text, player, server))
.modifyText(text -> event.parsePAPIPlaceholders(text, player))
.applyMMTags(event.plugin())
.toString();
try{
online = onlinePlayers == null ? online : Integer.parseInt(onlinePlayers);
event.onlinePlayers(online);
event.setOnlinePlayers(online);
}catch(NumberFormatException ex){
logger.warn("Option 'playerCount -> onlinePlayers -> amount' is not a valid Number!");
}
@@ -111,14 +110,13 @@ public class PingEventHandler{
String extraPlayers = ComponentParser.text(entry.extraPlayersCount())
.modifyText(text -> StringReplacer.replace(text, player, server))
.modifyText(text -> event.parsePAPIPlaceholders(text, player))
.applyMMTags(event.plugin())
.toString();
try{
max = online + (extraPlayers == null ? 0 : Integer.parseInt(extraPlayers));
logger.debug(PingEventHandler.class, "'playerCount -> extraPlayers -> amount' is a valid number. Applying it...");
event.maxPlayers(max);
event.setMaxPlayers(max);
}catch(NumberFormatException ex){
logger.warn("Option 'playerCount -> extraPlayers -> amount' is not a valid Number!");
}
@@ -129,20 +127,19 @@ public class PingEventHandler{
String maxPlayers = ComponentParser.text(entry.maxPlayersCount())
.modifyText(text -> StringReplacer.replace(text, player, server))
.modifyText(text -> event.parsePAPIPlaceholders(text, player))
.applyMMTags(event.plugin())
.toString();
try{
max = maxPlayers == null ? 0 : Integer.parseInt(maxPlayers);
logger.debug(PingEventHandler.class, "'playerCount -> maxPlayers -> amount' is a valid number. Applying it...");
event.maxPlayers(max);
event.setMaxPlayers(max);
}catch(NumberFormatException ex){
logger.warn("Option 'playerCount -> maxPlayers -> amount' is not a valid Number!");
}
}
GenericServer finalServer = event.createServer(online, max, host);
GenericServer finalServer = event.createGenericServer(online, max, host);
if(ProfileManager.checkOption(entry.motd()) && ignoreMaintenance(event, "motd")){
logger.debug(PingEventHandler.class, "'motd' option present. Applying ['%s']...", String.join("', '", entry.motd()));
@@ -151,13 +148,12 @@ public class PingEventHandler{
.map(line -> ComponentParser.text(line)
.modifyText(text -> StringReplacer.replace(text, player, finalServer))
.modifyText(text -> event.parsePAPIPlaceholders(text, player))
.modifyText(text -> plugin.core().textCenterUtil().getCenteredText(text))
.applyMMTags(event.plugin())
.modifyText(text -> plugin.getCore().getTextCenterUtil().getCenteredText(text))
.toComponent()
)
.toList();
event.motd(Component.join(JoinConfiguration.separator(Component.newline()), motd));
event.setMotd(Component.join(JoinConfiguration.separator(Component.newline()), motd));
}
boolean hidePlayers = ProfileManager.checkOption(entry.hidePlayersEnabled());
@@ -171,11 +167,10 @@ public class PingEventHandler{
if(ProfileManager.checkOption(entry.playerCountText()) && !hidePlayers && ignoreMaintenance(event, "playerCountText")){
logger.debug(PingEventHandler.class, "'playerCount -> text' option set. Applying '%s'...", entry.playerCountText());
event.playerCount(
event.setPlayerCount(
ComponentParser.text(entry.playerCountText())
.modifyText(text -> StringReplacer.replace(text, player, finalServer))
.modifyText(text -> event.parsePAPIPlaceholders(text, player))
.applyMMTags(event.plugin())
.toString()
);
}
@@ -183,33 +178,30 @@ public class PingEventHandler{
boolean hidePlayersHover = ProfileManager.checkOption(entry.hidePlayersHoverEnabled());
if(hidePlayersHover){
logger.debug(PingEventHandler.class, "'playerCount -> hidePlayersHover' option set. Hiding Player Hover...");
event.playersHidden();
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.players(entry.players(), player, server);
event.setPlayers(entry.players(), player, server);
}
if(ProfileManager.checkOption(entry.favicon()) && ignoreMaintenance(event, "favicon")){
logger.debug(PingEventHandler.class, "'favicon' option set. Resolving '%s'...", entry.favicon());
String faviconString = StringReplacer.modifier(entry.favicon())
.modify(text -> StringReplacer.replace(text, player, server))
.modify(text -> event.parsePAPIPlaceholders(text, player))
.asString();
String faviconString = StringReplacer.replace(entry.favicon(), player, server);
F favicon = plugin.faviconHandler().favicon(faviconString);
F favicon = plugin.getFaviconHandler().getFavicon(faviconString);
if(favicon == null){
logger.debugWarn(PingEventHandler.class, "Favicon was invalid or not yet resolved! Using default favicon of Server/Proxy...");
event.defaultFavicon();
event.setDefaultFavicon();
}else{
logger.debug(PingEventHandler.class, "Applying favicon...");
event.favicon(favicon);
event.setFavicon(favicon);
}
}
@@ -223,6 +215,6 @@ public class PingEventHandler{
if(!event.isMaintenanceModeActive())
return true;
return !event.plugin().core().fileHandler().getBool(true, "disableDuringMaintenance", option);
return !event.getPlugin().getCore().getFileHandler().getBool(true, "disableDuringMaintenance", option);
}
}
@@ -46,9 +46,7 @@ public class ConfigMigrator{
public static ConfigurationTransformation.Versioned create(){
return ConfigurationTransformation.versionedBuilder()
.versionKey("configVersion")
.addVersion(LATEST, nineToTen())
.addVersion(9, eightToNine())
.addVersion(8, sevenToEight())
.addVersion(LATEST, sevenToEight())
.addVersion(7, sixToSeven())
.addVersion(6, fiveToSix())
.addVersion(5, fourToFive())
@@ -58,22 +56,6 @@ public class ConfigMigrator{
.build();
}
public static ConfigurationTransformation nineToTen(){
return ConfigurationTransformation.builder()
.addAction(NodePath.path("faviconStrategy"), TransformAction.rename("faviconResizeStrategy"))
.build();
}
public static ConfigurationTransformation eightToNine(){
return ConfigurationTransformation.builder()
.addAction(NodePath.path(), (path, value) -> {
value.node("faviconStrategy").set("resize");
return null;
})
.build();
}
public static ConfigurationTransformation sevenToEight(){
return ConfigurationTransformation.builder()
.addAction(NodePath.path("disableDuringMaintenance"), (path, value) -> {
@@ -32,7 +32,6 @@ import ch.andre601.advancedserverlist.core.profiles.ServerListProfile;
import ch.andre601.advancedserverlist.core.profiles.profile.ProfileSerializer;
import org.spongepowered.configurate.ConfigurateException;
import org.spongepowered.configurate.ConfigurationNode;
import org.spongepowered.configurate.serialize.SerializationException;
import org.spongepowered.configurate.yaml.NodeStyle;
import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
@@ -47,7 +46,6 @@ import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
public class FileHandler{
@@ -63,27 +61,18 @@ public class FileHandler{
public FileHandler(AdvancedServerList<?> core){
this.plugin = core;
this.logger = core.plugin().pluginLogger();
this.logger = core.getPlugin().getPluginLogger();
this.config = core.plugin().folderPath().resolve("config.yml");
this.profilesFolder = core.plugin().folderPath().resolve("profiles");
this.config = core.getPlugin().getFolderPath().resolve("config.yml");
this.profilesFolder = core.getPlugin().getFolderPath().resolve("profiles");
}
public List<ServerListProfile> profiles(){
public List<ServerListProfile> getProfiles(){
return profiles;
}
public ServerListProfile profile(String file){
for(ServerListProfile profile : profiles){
if(profile.file().equalsIgnoreCase(file))
return profile;
}
return null;
}
public boolean loadConfig(){
File folder = plugin.plugin().folderPath().toFile();
File folder = plugin.getPlugin().getFolderPath().toFile();
if(!folder.exists() && !folder.mkdirs()){
logger.warn("Couldn't create folder for plugin. Is it missing Write permissions?");
return false;
@@ -117,48 +106,6 @@ public class FileHandler{
}
}
public boolean saveProfile(String file, int priority, String condition, List<ProfileEntry> profiles, ProfileEntry defaultProfile){
String filename = file.toLowerCase(Locale.ROOT);
Path filePath = profilesFolder.resolve(filename.endsWith(".yml") ? filename : filename + ".yml");
if(!Files.exists(filePath)){
logger.warn("Cannot find profile with file name '<white>%s</white>'", filename);
return false;
}
YamlConfigurationLoader loader = YamlConfigurationLoader.builder()
.defaultOptions(opts -> opts.serializers(build -> build.register(ProfileEntry.class, ProfileSerializer.INSTANCE)))
.path(filePath)
.nodeStyle(NodeStyle.BLOCK)
.build();
ConfigurationNode node;
try{
node = loader.load();
}catch(IOException ex){
logger.warn("Encountered an IOException while loading file '<white>%s</white>'", ex, filename);
return false;
}
if(node == null)
return false;
try{
node.node("priority").set(priority);
node.node("condition").set(condition);
node.node("profiles").setList(ProfileEntry.class, profiles);
node.set(ProfileEntry.class, defaultProfile);
loader.save(node);
return true;
}catch(SerializationException ex){
logger.warn("Encountered a SerializationException while saving file '<white>%s</white>'", ex, filename);
return false;
}catch(IOException ex){
logger.warn("Encountered an IOException while saving file '<white>%s</white>'", ex, filename);
return false;
}
}
public boolean createFile(String name){
if(Files.notExists(profilesFolder) && profilesFolder.toFile().mkdirs()){
logger.debugSuccess(FileHandler.class, "Successfully created profiles folder.");
@@ -272,7 +219,7 @@ public class FileHandler{
private boolean makeBackup(){
logger.info("[-] Making backup of old config.yml...");
File backups = plugin.plugin().folderPath().resolve("backups").toFile();
File backups = plugin.getPlugin().getFolderPath().resolve("backups").toFile();
if(!backups.exists() && !backups.mkdirs()){
logger.failure("Cannot create backups folder for migration!");
return false;
@@ -25,12 +25,12 @@
package ch.andre601.advancedserverlist.core.interfaces.commands;
import net.kyori.adventure.audience.Audience;
public interface CmdSender{
String prefix = "<grey>[<aqua>ASL</aqua>] ";
String errorPrefix = "<grey>[<red>ASL</red>] ";
String prefix = "<grey>[<gradient:aqua:white>AdvancedServerList</gradient>] ";
String errorPrefix = "<grey>[<gradient:dark_red:red>AdvancedServerList</gradient>] ";
boolean hasPermission(String permission);
void sendMsg(String msg, Object... args);
@@ -45,8 +45,4 @@ public interface CmdSender{
default void sendErrorMsg(String msg, Object... args){
sendMsg(errorPrefix + msg, args);
}
Audience audience();
boolean isPlayer();
}
@@ -1,7 +1,7 @@
/*
* MIT License
*
* Copyright (c) 2022-2025 Andre_601
* 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
@@ -20,22 +20,32 @@
* 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.interfaces.commands;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Locale;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface CommandType{
Type value();
public abstract class PluginCommand{
enum Type{
ALL,
PLAYER_ONLY
private final String name;
public PluginCommand(String name){
this.name = name;
}
public String name(){
return name;
}
public String permission(){
return "advancedserverlist.command." + name().toLowerCase(Locale.ROOT);
}
public abstract void handle(CmdSender sender, String[] args);
public abstract String usage();
public abstract String description();
}
@@ -26,19 +26,16 @@
package ch.andre601.advancedserverlist.core.interfaces.core;
import ch.andre601.advancedserverlist.core.AdvancedServerList;
import ch.andre601.advancedserverlist.core.interfaces.dialogs.IDialog;
import ch.andre601.advancedserverlist.core.interfaces.dialogs.IDialogList;
import ch.andre601.advancedserverlist.core.interfaces.PluginLogger;
import ch.andre601.advancedserverlist.core.interfaces.commands.CmdSender;
import ch.andre601.advancedserverlist.core.profiles.ServerListProfile;
import ch.andre601.advancedserverlist.core.profiles.handlers.FaviconHandler;
import org.incendo.cloud.CommandManager;
import java.awt.image.BufferedImage;
import java.nio.file.Path;
public interface PluginCore<F>{
void loadCommands();
void loadEvents();
void loadMetrics();
@@ -51,34 +48,26 @@ public interface PluginCore<F>{
void startScheduler();
AdvancedServerList<F> core();
AdvancedServerList<F> getCore();
Path folderPath();
Path getFolderPath();
PluginLogger pluginLogger();
PluginLogger getPluginLogger();
FaviconHandler<F> faviconHandler();
FaviconHandler<F> getFaviconHandler();
CommandManager<CmdSender> commandManager();
String getPlatformInfo();
String platformInfo();
String loader();
String getLoader();
boolean isPluginEnabled(String plugin);
boolean isDialogsSupported();
F createFavicon(BufferedImage image) throws Exception;
IDialogList.IDialogListBuilder createDialogListBuilder(String title);
IDialog.IDialogBuilder createDialogBuilder(String title, ServerListProfile profile, int profilesPosition);
default boolean isDebugEnabled(){
if(core() == null)
if(getCore() == null)
return false;
return core().fileHandler().getBoolean("debug");
return getCore().getFileHandler().getBoolean("debug");
}
}
@@ -1,23 +0,0 @@
package ch.andre601.advancedserverlist.core.interfaces.dialogs;
import ch.andre601.advancedserverlist.api.objects.NullBool;
import java.util.List;
public interface IDialog<T>{
T createDialog();
interface IDialogBuilder{
IDialogBuilder appendBody(String body);
IDialogBuilder appendTextInput(String key, String title, String initValue, int maxLength);
IDialogBuilder appendTextbox(String key, String title, List<String> initValue, int maxLength, int rows);
IDialogBuilder appendNumberRange(String key, String title, int initValue);
IDialogBuilder appendBoolean(String key, String title, NullBool initValue);
IDialog<?> build();
}
}
@@ -1,16 +0,0 @@
package ch.andre601.advancedserverlist.core.interfaces.dialogs;
import ch.andre601.advancedserverlist.core.interfaces.commands.CmdSender;
public interface IDialogList{
void show(CmdSender sender);
interface IDialogListBuilder{
IDialogListBuilder appendBody(String body);
IDialogListBuilder appendDialog(IDialog<?> dialog);
IDialogList build();
}
}
@@ -40,23 +40,23 @@ public interface GenericEventWrapper<F, P extends GenericPlayer>{
GenericServerListEvent callEvent(ProfileEntry entry);
void maxPlayers(int maxPlayers);
void setMaxPlayers(int maxPlayers);
void onlinePlayers(int onlinePlayers);
void setOnlinePlayers(int onlinePlayers);
void motd(Component component);
void setMotd(Component component);
void hidePlayers();
void playerCount(String name);
void setPlayerCount(String name);
void players(List<String> players, P player, GenericServer server);
void setPlayers(List<String> players, P player, GenericServer server);
void playersHidden();
void setPlayersHidden();
void favicon(F favicon);
void setFavicon(F favicon);
void defaultFavicon();
void setDefaultFavicon();
void updateEvent();
@@ -64,23 +64,23 @@ public interface GenericEventWrapper<F, P extends GenericPlayer>{
boolean isMaintenanceModeActive();
int protocolVersion();
int getProtocolVersion();
int onlinePlayers();
int getOnlinePlayers();
int maxPlayers();
int getMaxPlayers();
String playerIP();
String getPlayerIP();
String parsePAPIPlaceholders(String text, P player);
String virtualHost();
String getVirtualHost();
PluginCore<F> plugin();
PluginCore<F> getPlugin();
P createPlayer(CachedPlayer player, int protocol);
GenericServer createServer(int playersOnline, int playersMax, String host);
GenericServer createGenericServer(int playersOnline, int playersMax, String host);
default String resolveHost(InetSocketAddress address){
return address == null ? null : address.getHostString();
@@ -49,14 +49,12 @@ import java.util.List;
public class MiniMOTDConfigMigrator{
public static boolean migrate(AdvancedServerList<?> core, CmdSender sender){
sender.sendMsg("Loading Configurate-Hocon library. This may take a while...");
core.plugin().downloadLibrary("org.spongepowered", "configurate-hocon", "4.1.2");
sender.sendMsg("<green>Library loaded!");
core.getPlugin().downloadLibrary("org.spongepowered", "configurate-hocon", "4.1.2");
PluginLogger logger = core.plugin().pluginLogger();
String folder = core.plugin().loader().equals("velocity") ? "minimotd-velocity" : "MiniMOTD";
PluginLogger logger = core.getPlugin().getPluginLogger();
String folder = core.getPlugin().getLoader().equals("velocity") ? "minimotd-velocity" : "MiniMOTD";
Path mainConf = core.plugin().folderPath().getParent().resolve(folder).resolve("main.conf");
Path mainConf = core.getPlugin().getFolderPath().getParent().resolve(folder).resolve("main.conf");
if(!Files.exists(mainConf)){
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.");
@@ -100,7 +98,7 @@ public class MiniMOTDConfigMigrator{
if(config.motdEnabled()){
if(config.motds().size() == 1){
applyMotd(profileBuilder, config.motds().getFirst());
applyMotd(profileBuilder, config.motds().get(0));
}else
if(config.motds().size() > 1){
for(int i = 0; i < config.motds().size(); i++){
@@ -111,7 +109,7 @@ public class MiniMOTDConfigMigrator{
if(config.iconEnabled()){
if(config.motds().size() == 1){
MiniMOTDConfig.Motd motd = config.motds().getFirst();
MiniMOTDConfig.Motd motd = config.motds().get(0);
if(!motd.icon().isEmpty()){
profileBuilder.favicon(motd.icon());
}
@@ -154,7 +152,7 @@ public class MiniMOTDConfigMigrator{
return false;
}
Path profile = core.plugin().folderPath().resolve("profiles").resolve("minimotd_migrated.yml");
Path profile = core.getPlugin().getFolderPath().resolve("profiles").resolve("minimotd_migrated.yml");
if(Files.exists(profile)){
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.");
@@ -99,7 +99,7 @@ public class SLPConfigMigrator{
public static int migrate(AdvancedServerList<?> core, CmdSender sender){
ServerListPlusCore slpCore = ServerListPlusCore.getInstance();
PluginLogger logger = core.plugin().pluginLogger();
PluginLogger logger = core.getPlugin().getPluginLogger();
if(slpCore == null){
logger.warn("[<white>Migrator - ServerListPlus</white>] Cannot migrate ServerListPlus config. ServerListPlus is not active!");
return 0;
@@ -128,7 +128,7 @@ public class SLPConfigMigrator{
}
private static int parseConf(AdvancedServerList<?> core, CmdSender sender, PersonalizedStatusConf.StatusConf conf, Type type){
PluginLogger logger = core.plugin().pluginLogger();
PluginLogger logger = core.getPlugin().getPluginLogger();
if(conf == null){
logger.info("[<white>Migrator - ServerListPlus</white>] No StatusConf found for type <white>%s</white>. Skipping...", type.getName());
sender.sendPrefixedMsg(" -> Not found! Skipping...");
@@ -176,7 +176,7 @@ public class SLPConfigMigrator{
return 0;
}
Path profile = core.plugin().folderPath().resolve("profiles").resolve(type.getFile());
Path profile = core.getPlugin().getFolderPath().resolve("profiles").resolve(type.getFile());
if(Files.exists(profile)){
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());
@@ -223,7 +223,7 @@ public class SLPConfigMigrator{
if(type == Type.PERSONALIZED){
node.node("condition")
.set("${player name} != \"" + core.fileHandler().getString("Anonymous", "unknownPlayer", "name") + "\"");
.set("${player name} != \"" + core.getFileHandler().getString("Anonymous", "unknownPlayer", "name") + "\"");
}else
if(type == Type.BANNED){
node.node("condition")
@@ -0,0 +1,85 @@
/*
* MIT License
*
* Copyright (c) 2022-2024 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.objects;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
public abstract class CacheUtil{
protected final Duration duration;
public CacheUtil(Duration duration){
this.duration = duration;
}
public static class SingletonCache<T> extends CacheUtil{
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();
}
}
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){}
}
@@ -25,11 +25,8 @@
package ch.andre601.advancedserverlist.core.parsing;
import ch.andre601.advancedserverlist.core.compat.miniplaceholders.MiniPlaceholdersUtil;
import ch.andre601.advancedserverlist.core.interfaces.core.PluginCore;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import java.util.function.Function;
@@ -64,17 +61,6 @@ public class ComponentParser{
return this;
}
public ComponentParser applyMMTags(PluginCore<?> plugin){
if(!plugin.isPluginEnabled("MiniPlaceholders"))
return this;
TagResolver resolver = MiniPlaceholdersUtil.tagResolvers();
Component component = mm.deserialize(this.text, resolver);
this.text = mm.serialize(component);
return this;
}
public Component toComponent(){
return mm.deserialize(text);
}
@@ -77,8 +77,8 @@ public class TextCenterUtil{
private Map<String, FontInfo> loadFontWidths(AdvancedServerList<?> plugin){
try(InputStream stream = plugin.getClass().getResourceAsStream("/char-widths.json.gz")){
if(stream == null){
plugin.plugin().pluginLogger().warn("Internal char-widths.json.gz couldn't be retrieved.");
plugin.plugin().pluginLogger().warn("The <center> Placeholder won't work.");
plugin.getPlugin().getPluginLogger().warn("Internal char-widths.json.gz couldn't be retrieved.");
plugin.getPlugin().getPluginLogger().warn("The <center> Placeholder won't work.");
return new HashMap<>();
}
@@ -87,9 +87,9 @@ public class TextCenterUtil{
return new Gson().fromJson(reader, new TypeToken<Map<String, FontInfo>>(){}.getType());
}catch(IOException ex){
plugin.plugin().pluginLogger().warn("Encountered IOException while loading font widths.");
plugin.plugin().pluginLogger().warn("The <center> Placeholder won't work.");
plugin.plugin().pluginLogger().warn("Cause: <white>%s</white>", ex.getMessage());
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: <white>%s</white>", ex.getMessage());
return new HashMap<>();
}
}
@@ -1,81 +0,0 @@
package ch.andre601.advancedserverlist.core.placeholders;
import ch.andre601.advancedserverlist.api.PlaceholderProvider;
import ch.andre601.advancedserverlist.api.objects.GenericPlayer;
import ch.andre601.advancedserverlist.api.objects.GenericServer;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.function.Function;
public class DatePlaceholder extends PlaceholderProvider{
public DatePlaceholder(){
super("date");
}
@Override
public @Nullable String parsePlaceholder(@NonNull String placeholder, @NonNull GenericPlayer player, @NonNull GenericServer server){
String[] args = placeholder.split("\\s+");
return switch(args[0]){
case "current" -> {
String format = "dd/MM/yyyy";
if(args.length > 1){
format = format(Arrays.copyOfRange(args, 1, args.length));
}
try{
SimpleDateFormat dateFormat = new SimpleDateFormat(format);
yield dateFormat.format(new Date());
}catch(IllegalArgumentException ex){
yield "ERR: Invalid Date pattern " + format;
}
}
case "isBefore" -> compareDates(args, integer -> integer < 0);
case "isExactly" -> compareDates(args, integer -> integer == 0);
case "isAfter" -> compareDates(args, integer -> integer > 0);
default -> null;
};
}
private String format(String... args){
if(args.length == 0)
return "dd/MM/yyyy";
String format = String.join(" ", args);
if(format.trim().isEmpty()){
return "dd/MM/yyyy";
}else{
return format.trim();
}
}
private String compareDates(String[] args, Function<Integer, Boolean> function){
if(args.length == 1)
return "ERR: No Date provided!";
String targetDateStr = args[1];
String format = "dd/MM/yyyy";
if(args.length > 3){
format = format(Arrays.copyOfRange(args, 2, args.length));
}
SimpleDateFormat dateFormat;
try{
dateFormat = new SimpleDateFormat(format);
Date targetDate = dateFormat.parse(targetDateStr);
int diff = new Date().compareTo(targetDate);
return String.valueOf(function.apply(diff));
}catch(IllegalArgumentException ex){
return "ERR: Invalid Date format provided!";
}catch(ParseException ex){
return "ERR: Received invalid Date " + targetDateStr;
}
}
}
@@ -46,9 +46,9 @@ public record ServerListProfile(int priority, String condition, ProfileEntry def
return true;
ParseWarnCollector collector = new ParseWarnCollector(condition);
PluginLogger logger = core.plugin().pluginLogger();
PluginLogger logger = core.getPlugin().getPluginLogger();
boolean result = core.parser().evaluate(condition, player, server, collector);
boolean result = core.getParser().evaluate(condition, player, server, collector);
if(collector.hasWarnings()){
logger.warn("Encountered <white>%d</white> Error(s) while parsing condition for file '<white>%s</white>':", collector.getWarnings().size(), file);
@@ -1,9 +1,33 @@
/*
* MIT License
*
* Copyright (c) 2022-2024 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.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.*;
@@ -17,8 +41,9 @@ import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.time.Duration;
import java.util.List;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
@@ -30,309 +55,175 @@ 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 Random random = new Random();
private final Map<String, F> localFavicons = new HashMap<>();
private final HttpClient client = HttpClient.newHttpClient();
private final Map<String, FaviconPair<F>> localFavicons = new HashMap<>();
private final Random random = new Random();
public FaviconHandler(AdvancedServerList<F> core){
this.core = core;
this.logger = core.plugin().pluginLogger();
this.faviconThreadPool = createThreadPool();
this.faviconCache = CacheBuilder.newBuilder()
.expireAfterWrite(core.fileHandler().getLong(1, 1, "faviconCacheTime"), TimeUnit.MINUTES)
.build();
this.logger = core.getPlugin().getPluginLogger();
this.faviconThreadPool = createFaviconThreadPool();
this.faviconCache = new CacheUtil.IdentifiableCache<>(Duration.ofMinutes(
core.getFileHandler().getLong(1, 1, "faviconCacheTime")
));
loadLocalFavicons();
}
public F favicon(String input){
if(localFavicons.containsKey(input.toLowerCase(Locale.ROOT))){
logger.debug(FaviconHandler.class, "Returning local Favicon '%s'.", input);
return localFavicons.get(input.toLowerCase(Locale.ROOT)).favicon();
}
if(input.equalsIgnoreCase("random")){
logger.debug(FaviconHandler.class, "Returning random local Favicon.");
FaviconPair<F> pair = randomFavicon();
if(pair == null){
logger.warn("Cannot return random Favicon. Make sure PNGs are available in the Favicons folder!");
return null;
public F getFavicon(String input){
if(!localFavicons.isEmpty()){
logger.debug(FaviconHandler.class, "Current Local Favicons:");
for(String key : localFavicons.keySet()){
logger.debug(FaviconHandler.class, " - %s", key);
}
return pair.favicon();
}
CompletableFuture<F> favicon = faviconCache.getIfPresent(input);
if(favicon != null)
return favicon.getNow(null);
logger.debug(FaviconHandler.class, "Processing '%s'", input);
if(input.contains(";")){
String[] parts = input.split(";");
logger.debug(FaviconHandler.class, "Handling multi-part Favicon: ['%s']", String.join("', '", parts));
favicon = CompletableFuture.supplyAsync(() -> {
List<BufferedImage> images = new ArrayList<>();
Arrays.stream(parts).forEach(part -> {
logger.debug(FaviconHandler.class, "Creating BufferedImage for part '%s'...", part);
images.add(create(part));
});
if(images.isEmpty()){
logger.debugWarn(FaviconHandler.class, "Unable to create Multi-part Favicon. List was empty.");
return null;
}
return composeFavicon(images);
}, faviconThreadPool);
}else{
logger.debug(FaviconHandler.class, "Handling Favicon input '%s'...", input);
favicon = CompletableFuture.supplyAsync(() -> {
logger.debug(FaviconHandler.class, "Creating BufferedImage for '%s'...", input);
BufferedImage image = create(input);
if(image == null){
logger.warn("Cannot create Favicon for '%s'. BufferedImage was null!", input);
return null;
}
return createFavicon(image);
}, faviconThreadPool);
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));
}
faviconCache.put(input, favicon);
return favicon.getNow(null);
logger.debug(FaviconHandler.class, "Getting Favicon image from cache...");
return faviconCache.get(input, () -> getFuture(input)).getNow(null);
}
public void cleanCache(){
localFavicons.clear();
faviconCache.invalidateAll();
faviconCache.clear();
loadLocalFavicons();
}
private F composeFavicon(List<BufferedImage> images){
logger.debug(FaviconHandler.class, "Composing Favicon out of %d part(s)...", images.size());
BufferedImage image = new BufferedImage(64, 64, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = image.createGraphics();
String strategy = core.fileHandler().getString("resize", "faviconStrategy");
int layer = 0;
for(BufferedImage img : images){
layer++;
logger.debug(FaviconHandler.class, "Applying layer %d...", layer);
if(img == null){
logger.debugWarn(FaviconHandler.class, "Layer %d was null! Skipping...", layer);
continue;
}
switch(strategy.toLowerCase(Locale.ROOT)){
case "center" -> {
int height = img.getHeight();
int width = img.getWidth();
int posHeight = 0;
int posWidth = 0;
if(height != 64){
posHeight = (64 - height) / 2;
}
// 64 - 128 = -64
if(width != 64){
posWidth = (64 - width) / 2;
}
graphics.drawImage(img, posWidth, posHeight, img.getWidth(), img.getHeight(), null);
}
case "none" -> graphics.drawImage(img, 0, 0, img.getWidth(), img.getHeight(), null);
default -> graphics.drawImage(img, 0, 0, 64, 64, null);
}
logger.debug(FaviconHandler.class, "Applied layer %d!", layer);
}
graphics.dispose();
logger.debug(FaviconHandler.class, "Image Composing complete! Creating Favicon instance...");
return createFavicon(image);
}
private BufferedImage create(String input){
private CompletableFuture<F> getFuture(String input){
if(input.toLowerCase(Locale.ROOT).startsWith("https://")){
logger.debug(FaviconHandler.class, "Handling URL '%s'...", input);
return fromURL(input);
logger.debug(FaviconHandler.class, "Resolving URL '%s'...", input);
return CompletableFuture.supplyAsync(() -> fromURL(core, input), this.faviconThreadPool);
}else
if(input.equalsIgnoreCase("random")){
logger.debug(FaviconHandler.class, "Returning random local Favicon...");
FaviconPair<F> pair = randomFavicon();
return pair == null ? null : pair.image();
return CompletableFuture.completedFuture(getRandomized());
}else
if(input.toLowerCase(Locale.ROOT).endsWith(".png")){
logger.debug(FaviconHandler.class, "Returning local Favicon '%s'...", input);
FaviconPair<F> pair = localFavicons.get(input.toLowerCase(Locale.ROOT));
return pair == null ? null : pair.image();
logger.debug(FaviconHandler.class, "Resolving image file '%s'...", input);
return CompletableFuture.completedFuture(localFavicons.get(input.toLowerCase(Locale.ROOT)));
}else{
logger.debug(FaviconHandler.class, "Handling Player Name/UUID '%s'...", input);
return fromURL("https://mc-heads.net/avatar/" + input + "/64");
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);
}
}
private FaviconPair<F> randomFavicon(){
private F getRandomized(){
logger.debug(FaviconHandler.class, "Obtaining random Favicon...");
if(localFavicons.isEmpty())
return null;
List<FaviconPair<F>> favicons = new ArrayList<>(localFavicons.values());
if(favicons.isEmpty())
return null;
List<F> faviconList = List.copyOf(localFavicons.values());
if(favicons.size() == 1)
return favicons.getFirst();
// Don't use Random for just one entry.
if(faviconList.size() == 1)
return faviconList.get(0);
synchronized(random){
return favicons.get(random.nextInt(favicons.size()));
return faviconList.get(random.nextInt(faviconList.size()));
}
}
private void loadLocalFavicons(){
this.localFavicons.clear();
logger.info("[-] Loading local images as Favicons...");
logger.info("[-] Loading local image files as Favicons...");
Path folder = core.plugin().folderPath().resolve("favicons");
Path folder = core.getPlugin().getFolderPath().resolve("favicons");
if(!Files.exists(folder)){
try{
Files.createDirectories(folder);
logger.debugSuccess(FaviconHandler.class, "Created favicons folder");
logger.debugSuccess(FaviconHandler.class, "Created favicons folder.");
}catch(IOException ex){
logger.warn("Encountered IOException while creating favicons folder.", ex);
logger.failure("Cannot create favicons folder. Encountered an IOException.", ex);
return;
}
}
try(Stream<Path> stream = Files.list(folder)){
stream.filter(Files::isRegularFile)
.filter(file -> file.getFileName().toString().endsWith(".png"))
.forEach(this::loadFile);
try(Stream<Path> pathStream = Files.list(folder)){
pathStream.filter(Files::isRegularFile)
.filter(file -> file.getFileName().toString().endsWith(".png"))
.forEach(this::loadFile);
logger.success("Loaded <white>%s</white> local Favicon(s).", localFavicons.size());
logger.success("Loaded <white>%d</white> local Favicons.", localFavicons.size());
}catch(IOException ex){
logger.warn("Encountered IOException while loading local favicons.", ex);
logger.failure("Cannot load files from favicons folder.", ex);
}
}
private void loadFile(Path path){
try(InputStream stream = Files.newInputStream(path)){
BufferedImage image = createBufferedImage(stream);
if(image == null)
F favicon = createFavicon(stream);
if(favicon == null){
logger.failure("Cannot create Favicon from file '<white>%s</white>'. Received Favicon was null.", path.getFileName().toString());
return;
}
F favicon = core.plugin().createFavicon(image);
if(favicon == null)
return;
localFavicons.put(path.getFileName().toString().toLowerCase(Locale.ROOT), new FaviconPair<>(image, favicon));
localFavicons.put(path.getFileName().toString().toLowerCase(Locale.ROOT), favicon);
logger.debugSuccess(FaviconHandler.class, "Loaded file '<white>%s</white>' as Favicon.", path.getFileName().toString());
}catch(IOException ex){
logger.warn("Encountered IOException while creating a Favicon from '%s'.", ex, path.getFileName().toString());
}catch(Exception ex){
logger.warn("Encountered an Exceotion while creating a Favicon from '%s'.", ex, path.getFileName().toString());
logger.failure("Cannot create Favicon from file '<white>%s</white>'. Encountered IOException.", ex);
}
}
private BufferedImage fromURL(String url){
private F fromURL(AdvancedServerList<F> core, String url){
try{
logger.debug(FaviconHandler.class, "Creating request for URL '%s'...", url);
logger.debug(FaviconHandler.class, "Creating Request for URL '%s'...", url);
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI(url))
.header("User-Agent", "AdvancedServerList-" + core.plugin().loader() + "/" + core.version())
.build();
.uri(new URI(url))
.header("User-Agent", "AdvancedServerList-" + core.getPlugin().getLoader() + "/" + core.getVersion())
.build();
try(InputStream stream = client.send(request, HttpResponse.BodyHandlers.ofInputStream()).body()){
return createBufferedImage(stream);
}catch(IOException ex){
logger.warn("Encountered IOException while performing a web request for '%s'.", ex, url);
return null;
}catch(InterruptedException ex){
logger.warn("Encountered an InterruptedException while performing a web request for '%s'.", ex, url);
return createFavicon(stream);
}catch(InterruptedException | IOException ex){
logger.warn("Cannot create Favicon from URL '<white>%s</white>'. Encountered an Exception.", ex, url);
return null;
}
}catch(URISyntaxException ex){
logger.warn("Received URISyntaxException for URL '%s'", ex, url);
logger.warn("Cannot create Favicon from URL '<white>%s</white>'. Encountered a URISyntaxException.", ex, url);
return null;
}
}
private BufferedImage createBufferedImage(InputStream stream){
logger.debug(FaviconHandler.class, "Creating BufferedImage from InputStream...");
private F createFavicon(InputStream stream){
try{
logger.debug(FaviconHandler.class, "Creating BufferedImage from InputStream...");
BufferedImage original = ImageIO.read(stream);
if(original == null){
logger.warn("Cannot create Favicon. Received a null BufferedImage!");
logger.warn("Cannot create Favicon. Received null BufferedImage.");
return null;
}
logger.debug(FaviconHandler.class, "Check image size for possible resizing...");
if(original.getHeight() == 64 && original.getWidth() == 64){
logger.debug(FaviconHandler.class, "Image is 64x64. No resizing needed!");
return original;
// Don't waste resources resizing images already having right size.
if(original.getWidth() == 64 && original.getHeight() == 64){
logger.debug(FaviconHandler.class, "BufferedImage is 64x64 pixels. No resizing needed.");
return core.getPlugin().createFavicon(original);
}
logger.debug(FaviconHandler.class, "Applying Favicon resize strategy...");
logger.debug(FaviconHandler.class, "Resizing image to 64x64 pixels...");
BufferedImage image = new BufferedImage(64, 64, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = image.createGraphics();
Graphics2D graphics2D = image.createGraphics();
String resizeMode = core.fileHandler().getString("resize", "faviconStrategy");
switch(resizeMode.toLowerCase(Locale.ROOT)){
case "center" -> {
int height = original.getHeight();
int width = original.getWidth();
int posHeight = 0;
int posWidth = 0;
if(height != 64){
posHeight = (64 - height) / 2;
}
if(width != 64){
posWidth = (64 - width) / 2;
}
graphics.drawImage(original, posWidth, posHeight, original.getWidth(), original.getHeight(), null);
}
case "none" -> graphics.drawImage(original, 0, 0, original.getWidth(), original.getHeight(), null);
default -> graphics.drawImage(original, 0, 0, 64, 64, null);
}
graphics2D.drawImage(original, 0, 0, 64, 64, null);
graphics2D.dispose();
graphics.dispose();
logger.debug(FaviconHandler.class, "Image resized. Returning it...");
return image;
}catch(IOException ex){
logger.warn("Encountered an IOException while processing Favicon!", ex);
return null;
}
}
private F createFavicon(BufferedImage image){
try{
return core.plugin().createFavicon(image);
logger.debug(FaviconHandler.class, "Image resized! Returning Favicon...");
return core.getPlugin().createFavicon(image);
}catch(Exception ex){
logger.warn("Encountered an Exception while trying to create a Favicon instance.", ex);
logger.warn("Unable to create Favicon. Received Exception.", ex);
return null;
}
}
private ThreadPoolExecutor createThreadPool(){
private ThreadPoolExecutor createFaviconThreadPool(){
return new ThreadPoolExecutor(3, 3, 60L, TimeUnit.SECONDS, new LinkedBlockingDeque<>(1024), r -> {
Thread t = new Thread(r, "AdvancedServerList-FaviconThread");
t.setDaemon(true);
return t;
});
}
private record FaviconPair<F>(BufferedImage image, F favicon){}
}
@@ -61,8 +61,8 @@ public class PlayerHandler{
public PlayerHandler(AdvancedServerList<?> core){
this.core = core;
this.logger = core.plugin().pluginLogger();
this.cache = core.plugin().folderPath().resolve("playercache.json");
this.logger = core.getPlugin().getPluginLogger();
this.cache = core.getPlugin().getFolderPath().resolve("playercache.json");
}
public void load(){
@@ -71,7 +71,7 @@ public class PlayerHandler{
return;
}
if(core.fileHandler().getBoolean("disableCache")){
if(core.getFileHandler().getBoolean("disableCache")){
logger.info("'disableCache' is set to true. Skipping playercache.json loading...");
return;
}
@@ -103,7 +103,7 @@ public class PlayerHandler{
return;
}
if(core.fileHandler().getBoolean("disableCache")){
if(core.getFileHandler().getBoolean("disableCache")){
logger.debug(PlayerHandler.class, "[-] 'disable_cache' is set to true. Not saving cached players.");
return;
}
@@ -117,14 +117,14 @@ public class PlayerHandler{
}
public void addPlayer(String ip, String name, UUID uuid){
if(contains(ip) || core.fileHandler().getBoolean("disableCache"))
if(contains(ip) || core.getFileHandler().getBoolean("disableCache"))
return;
cachedPlayers.add(new CachedPlayer(ip, name, uuid));
}
public CachedPlayer getCachedPlayer(String key){
if(!contains(key) || core.fileHandler().getBoolean("disableCache"))
if(!contains(key) || core.getFileHandler().getBoolean("disableCache"))
return getDefaultPlayer();
for(CachedPlayer player : cachedPlayers){
@@ -164,8 +164,8 @@ public class PlayerHandler{
return (defaultPlayer = new CachedPlayer(
"0.0.0.0",
core.fileHandler().getString("Anonymous", "unknownPlayer", "name"),
convertToUUID(core.fileHandler().getString(defaultUUID.toString(), "unknownPlayer", "uuid"))
core.getFileHandler().getString("Anonymous", "unknownPlayer", "name"),
convertToUUID(core.getFileHandler().getString(defaultUUID.toString(), "unknownPlayer", "uuid"))
));
}
@@ -78,7 +78,7 @@ public class ProfileManager{
}
public static ServerListProfile resolveProfile(AdvancedServerList<?> core, GenericPlayer player, GenericServer server){
for(ServerListProfile profile : core.fileHandler().profiles()){
for(ServerListProfile profile : core.getFileHandler().getProfiles()){
if(profile.isInvalidProfile())
continue;
@@ -120,13 +120,20 @@ public class ProfileManager{
}
public static boolean checkOption(Object obj){
return switch(obj){
case List<?> list -> !list.isEmpty(); // Check if list isn't empty
case String str -> !str.isEmpty(); // Check if String is not empty
case NullBool nb -> nb.getOrDefault(false); // Return NullBool's value
case null, default -> false;
};
if(obj == null)
return false;
if(obj instanceof List<?> list){
return !list.isEmpty(); // Check if list isn't empty
}else
if(obj instanceof String str){
return !str.isEmpty(); // Check if String is not empty
}else
if(obj instanceof NullBool nb){
return nb.getOrDefault(false); // Return NullBool's value
}
return false;
}
private static <T> T resolve(ProfileEntry defProfile, ProfileEntry profile, Function<ProfileEntry, T> function){
@@ -34,14 +34,9 @@ import ch.andre601.advancedserverlist.core.profiles.conditions.placeholders.toke
import ch.andre601.expressionparser.ParseWarnCollector;
import java.text.ParsePosition;
import java.util.function.Function;
public class StringReplacer{
public static StringModifier modifier(String text){
return new StringModifier(text);
}
public static String replace(String input, GenericPlayer player, GenericServer server){
if(input == null)
return null;
@@ -68,7 +63,7 @@ public class StringReplacer{
}
private static String parsePlaceholder0(String input, ParsePosition position, GenericPlayer player, GenericServer server, ParseWarnCollector collector){
AdvancedServerListAPI api = AdvancedServerList.api();
AdvancedServerListAPI api = AdvancedServerList.getApi();
int index = position.getIndex();
StringBuilder identifier = new StringBuilder();
@@ -153,22 +148,4 @@ public class StringReplacer{
return replacement;
}
public static class StringModifier{
private String value;
public StringModifier(String value){
this.value = value;
}
public StringModifier modify(Function<String, String> function){
this.value = function.apply(value);
return this;
}
public String asString(){
return value;
}
}
}
-13
View File
@@ -66,19 +66,6 @@ printBanner: true
#
faviconCacheTime: 1
#
# Sets how AdvancedServerList should handle the size and positioning of images for favicons.
#
# Available options:
#
# - resize: Resizes the image to 64x64 pixels if it isn't these dimensions already.
# - center: Doesn't resize the image, but instead tries to center it as good as possible in a 64x64 area.
# - none: Doesn't resize the image and leaves it positioned at x 0 and y 0 (top-left) of the 64x64 area.
#
# Setting any not listed option or leaving this blank/removing it sets the option to resize.
#
faviconResizeStrategy: 'resize'
#
# Options to disable while the Maintenance Plugin is present and Maintenance Mode is active.
#
+37 -43
View File
@@ -2,7 +2,7 @@
# Set the priority of this profile.
# If there are multiple profiles (files) will they be sorted by priority, starting with highest.
#
# Read more: https://asl.andre601.ch/profiles/options/priority/
# Read more: https://asl.andre601.ch/profiles/#priority
#
priority: 0
@@ -10,11 +10,11 @@ priority: 0
# Set condition(s) that should return true to display this profile.
# This allows you to create profiles to only show for specific scenarios (i.e. player is banned).
#
# 'AND'/'&&' and 'OR'/'||' can be used to chain together multiple conditions.
# Multiple conditions can be chaing with the keywords 'and', 'or', or their counterparts '&&' and '||'.
#
# Removing the condition option or setting it to an empty String will be treated as a true condition being returned.
# No condition or an empty condition will be treated as true by default.
#
# Read more: https://asl.andre601.ch/profiles/options/condition
# Read more: https://asl.andre601.ch/profiles/#condition
#
condition: ''
@@ -30,7 +30,7 @@ condition: ''
#
# When not present or empty (profiles: []), no profiles will be used and global options from the file will be used instead.
#
# Read more: https://asl.andre601.ch/profiles/options/profiles
# Read more: https://asl.andre601.ch/profiles/#profiles
#
profiles:
- motd: ['<aqua>Line A', '<gold>Line B']
@@ -40,17 +40,14 @@ profiles:
#
# Set the "Message of the day" to display.
# This option supports RGB colors for 1.16+ Servers and clients. More advanced MiniMessage features such as <gradient>
# can be used for further styling the text.
#
# <center> can be put at the start of a line to have AdvancedServerList automatically center the text as good as
# possible.
# This option supports RGB colors for 1.16+ Servers and clients, including gradients using MiniMessage's <gradient>
# option.
#
# Only the first two lines will be considered and any additional one is ignored.
#
# When not present or empty (motd: []), no MOTD will be set.
#
# Read more: https://asl.andre601.ch/profiles/options/motd
# Read more: https://asl.andre601.ch/profiles/#motd
#
motd:
- '<grey>First Line'
@@ -58,36 +55,33 @@ motd:
#
# Set the Favicon (image on the left of a server entry) to display.
# This option supports three types of input:
# This option supports three types of inputs:
#
# 1. Filename, including the .png extension, matching a filename in the favicons folder of the plugin.
# - 'random' can be used to have AdvancedServerList select a random image from the favicons folder every time
# the server is pinged.
# 1. Name, including .png extension, pointing to an image located in the plugin's favicons folder.
# 2. "random" to display a random PNG from the favicons folder.
# 3. URL pointing to an image.
# 4. Player name, UUID or a placeholder that returns either.
# - https://mc-heads.net will be used to retrieve an image of the provided name/UUID.
# 4. Player name, UUID or placeholders returning one of these.
# https://mc-heads.net will be used to retrieve an image of the provided name/UUID.
#
# Any image below or above 64x64 pixels will be resized according to the faviconResizeMode option in the config.yml.
# Note on favicons not showing:
# AdvancedServerList has to create a Favicon object for the Proxy/Server to use which takes time to do so.
# To not delay the Proxy/Server from being displayed (increase the loading time on the player end) is this creation
# done asynchronously for URL/Player name/UUID. This has the downside that the Proxy/Server may be done handling the
# ping before the Favicon is created, causing none to be displayed.
# Note that this won't happen with local favicons, as those are created during the plugin's startup.
#
# Any images above or below 64x64 pixels will be automatically resized to these dimensions.
#
# If not present or empty (favicon: ''), defaults to not modifying the favicon.
#
# Read more: https://asl.andre601.ch/profiles/options/favicon
#
# "The favicon isn't showing!"
# This is a side effect of using either URLs or player names/UUIDs for the favicon.
# These types require AdvancedServerList to create a new favicon object from scratch. This action takes a significant
# amount of time, which can cause the server to show with a huge ping.
# To avoid this is this action performed asynchronously. This means that the plugin doesn't wait for the favicon to
# be created before continuing, which can result in no favicon being available when it is done handling the ping.
# This should only happen for the first ping of a player and subsequent pings should display the favicon, if the
# pings are done within the configured cache time (See 'faviconCacheTime' in the config.yml).
# Read more: https://asl.andre601.ch/profiles/#favicon
#
favicon: ''
#
# Contains options for modifying the player count (Text displaying <online>/<max>).
#
# Read more: https://asl.andre601.ch/profiles/options/playercount
# Read more: https://asl.andre601.ch/profiles/#playercount
#
playerCount:
#
@@ -97,7 +91,7 @@ playerCount:
#
# If not present, defaults to false.
#
# Read more: https://asl.andre601.ch/profiles/options/playercount/hideplayers
# Read more: https://asl.andre601.ch/profiles/#hideplayers
#
hidePlayers: false
#
@@ -106,17 +100,17 @@ playerCount:
#
# If not present, defaults to false.
#
# Read more: https://asl.andre601.ch/profiles/options/playercount/hideplayershover
# 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
# down-sampled to the nearest matching color.
# downsampled to the nearest matching color.
#
# If not present or empty (hover: []), defaults to not modifying the hover.
#
# Read more: https://asl.andre601.ch/profiles/options/playercount/hover
# Read more: https://asl.andre601.ch/profiles/#hover
#
hover:
- '<aqua>Line 1'
@@ -130,7 +124,7 @@ playerCount:
#
# If not present or empty (text: ''), defaults to not modifying the text.
#
# Read more: https://asl.andre601.ch/profiles/options/playercount/text
# Read more: https://asl.andre601.ch/profiles/#text
#
text: ''
#
@@ -139,7 +133,7 @@ playerCount:
#
# Using this feature will modify the ${server playersMax} placeholder, except for usage in the condition option.
#
# Read more: https://asl.andre601.ch/profiles/options/playercount/extraplayers
# Read more: https://asl.andre601.ch/profiles/#extraplayers
#
extraPlayers:
#
@@ -149,7 +143,7 @@ playerCount:
#
# If not present, defaults to false.
#
# Read more: https://asl.andre601.ch/profiles/options/playercount/extraplayers#enabled
# Read more: https://asl.andre601.ch/profiles/#extraplayers-enabled
#
enabled: false
#
@@ -161,7 +155,7 @@ playerCount:
#
# If not present, defaults to 0.
#
# Read more: https://asl.andre601.ch/profiles/options/playercount/extraplayers#amount
# Read more: https://asl.andre601.ch/profiles/#extraplayers-amount
#
amount: 0
#
@@ -169,7 +163,7 @@ playerCount:
#
# Using this feature will modify the ${server playersMax} placeholder, except for usage in the condition option.
#
# Read more: https://asl.andre601.ch/profiles/options/playercount/maxplayers
# Read more: https://asl.andre601.ch/profiles/#maxplayers
#
maxPlayers:
#
@@ -178,7 +172,7 @@ playerCount:
#
# If not present, defaults to false.
#
# Read more: https://asl.andre601.ch/profiles/options/playercount/maxplayers#enabled
# Read more: https://asl.andre601.ch/profiles/#maxplayers-enabled
#
enabled: false
#
@@ -187,13 +181,13 @@ playerCount:
#
# If not present, defaults to 0.
#
# Read more: https://asl.andre601.ch/profiles/options/playercount/maxplayers#amount
# Read more: https://asl.andre601.ch/profiles/#maxplayers-amount
#
amount: 0
#
# The onlinePlayers feature allows to set a number to use as the online player count to display.
#
# Read more: https://asl.andre601.ch/profiles/options/playercount/onlineplayers
# Read more: https://asl.andre601.ch/profiles/#onlineplayers
#
onlinePlayers:
#
@@ -202,7 +196,7 @@ playerCount:
#
# If not present, defaults to false.
#
# Read more: https://asl.andre601.ch/profiles/options/playercount/onlineplayers#enabled
# Read more: https://asl.andre601.ch/profiles/#onlineplayers-enabled
#
enabled: false
#
@@ -211,6 +205,6 @@ playerCount:
#
# If not present, defaults to current online player count.
#
# Read more: https://asl.andre601.ch/profiles/options/playercount/onlineplayers#amount
# Read more: https://asl.andre601.ch/profiles/#onlineplayers-amount
#
amount: 0
-15
View File
@@ -1,15 +0,0 @@
def on_pre_page_macros(env):
footer = """
{% if page.meta and page.meta.api %}
{% include 'api_docs/constructor_summaries.md' %}
{% include 'api_docs/nested_class_summaries.md' %}
{% include 'api_docs/enum_summaries.md' %}
{% include 'api_docs/method_summaries.md' %}
{% include 'api_docs/constructor_details.md' %}
{% include 'api_docs/enum_details.md' %}
{% include 'api_docs/method_details.md' %}
{% endif %}
"""
env.markdown += footer
-23
View File
@@ -1,23 +0,0 @@
import re
from mkdocs.config.defaults import MkDocsConfig
from mkdocs.structure.files import Files
from mkdocs.structure.pages import Page
from re import Match
def on_page_markdown(
markdown: str, *, page: Page, config: MkDocsConfig, files: Files
):
def replace(match: Match):
platform = match.group(1)
if platform == "paper": return "[:fontawesome-solid-paper-plane:](https://papermc.io){ .md-button .md-button--icon title=\"PaperMC\" target=\"_blank\" rel=\"noopener\" }"
elif platform == "bungee": return "[:simple-spigotmc:](https://spigotmc.org){ .md-button .md-button--icon title=\"BungeeCord\" target=\"_blank\" rel=\"noopener\" }"
elif platform == "velocity": return "[:simple-velocity:](https://velocitypowered.com){ .md-button .md-button--icon title=\"Velocity\" target=\"_blank\" rel=\"noopener\" }"
elif platform == "proxies": return "[:simple-spigotmc:](https://spigotmc.org){ .md-button .md-button--icon title=\"BungeeCord\" target=\"_blank\" rel=\"noopener\" } [:simple-velocity:](https://velocitypowered.com){ .md-button .md-button--icon title=\"Velocity\" target=\"_blank\" rel=\"noopener\" }"
elif platform == "all": return "[:fontawesome-solid-paper-plane:](https://papermc.io){ .md-button .md-button--icon title=\"PaperMC\" target=\"_blank\" rel=\"noopener\" } [:simple-spigotmc:](https://spigotmc.org){ .md-button .md-button--icon title=\"BungeeCord\" target=\"_blank\" rel=\"noopener\" } [:simple-velocity:](https://velocitypowered.com){ .md-button .md-button--icon title=\"Velocity\" target=\"_blank\" rel=\"noopener\" }"
else: return platform
return re.sub(
r"<!-- icon:(paper|bungee|velocity|proxies|all) -->",
replace, markdown, flags = re.I | re.M
)
+35
View File
@@ -0,0 +1,35 @@
{% extends "base.html" %}
{% import "api-objects/info-template.html" as info %}
{% block content %}
{% include "partials/content.html" %}
{% if page.meta %}
<!--
Object summaries
-->
{% if page.meta.constructors %}
{{ info.summary('Constructor Summary', page.meta.constructors, details_type='constructor', defaults=page.meta.defaults) }}
{% endif %}
{% if page.meta.classes %}
{{ info.summary('Class Summary', page.meta.classes, details_type='class', defaults=page.meta.defaults) }}
{% endif %}
{% if page.meta.enums %}
{{ info.summary('Enum Constant Summary', page.meta.enums, details_type='enum', defaults=page.meta.defaults) }}
{% endif %}
{% if page.meta.methods or page.meta.inherits %}
{{ info.summary('Method Summary', page.meta.methods, page.meta.inherits, details_type='method', defaults=page.meta.defaults) }}
{% endif %}
<!--
Detailed object info
-->
{% if page.meta.constructors %}
{{ info.info('Constructor Detail', page.meta.constructors, 'constructor', defaults=page.meta.defaults) }}
{% endif %}
{% if page.meta.enums %}
{{ info.info('Enum Constant Detail', page.meta.enums, 'enum', defaults=page.meta.defaults) }}
{% endif %}
{% if page.meta.methods %}
{{ info.info('Method Detail', page.meta.methods, 'method', defaults=page.meta.defaults)}}
{% endif %}
{% endif %}
{% endblock %}
@@ -0,0 +1,228 @@
{% macro summary(title, summaries, inherits, details_type = 'unknown', defaults = []) -%}
<h2 id="{{ title | lower() | replace(' ', '-') | e}}">
{{ title }}
</h2>
<div class="md-typeset__scrollwrap">
<div class="md-typeset__table">
{% if summaries %}
<table>
<thead>
<tr>
{% if details_type and details_type == "method" %}
<th>Modifier and Type</th>
{% endif %}
<th>{{ details_type | capitalize() }}</th>
<th>Description</th>
</tr>
</thead>
<tbody>
{% for summary in summaries %}
<tr>
{% set modifiers = namespace(name = "") %}
{% for attribute in summary.attributes %}
{% set modifiers.name = modifiers.name ~ attribute %}
{% if loop.revindex > 0 %}{% set modifiers.name = modifiers.name ~ " " %}{% endif %}
{% endfor %}
{% if modifiers.name %}{% set modifiers.name = modifiers.name ~ " " %}{% endif %}
{% if summary.type and summary.type.name %}
{% set modifiers.name = modifiers.name ~ summary.type.name %}
{% elif defaults and defaults.type and defaults.type.name %}
{% set modifiers.name = modifiers.name ~ defaults.type.name %}
{% else %}
{% set modifiers.name = modifiers.name ~ "void" %}
{% endif %}
{% set desc = "" %}
{% if summary.deprecated %}
{% set desc = "<api__deprecated></api__deprecated><br>" ~ summary.deprecated.split("<br>")[0] %}
{% elif summary.description %}
{% set desc = summary.description.split("<br>")[0] %}
{% endif %}
{% set final_name = namespace(name = "") %}
{% if details_type and details_type == "enum" %}
{% set final_name.name = summary.name | upper() %}
{% else %}
{% set final_name.name = summary.name ~ "(" %}
{% endif %}
{% for parameter in summary.parameters %}
{% set final_name.name = final_name.name ~ parameter.type %}
{% if loop.revindex > 1 %}{% set final_name.name = final_name.name ~ ", " %}{% endif %}
{% endfor %}
{% if details_type != "enum" %}
{% set final_name.name = final_name.name ~ ")" %}
{% endif %}
{% if details_type and details_type == "method" %}
{% if modifiers.name %}
<td><code>{{ modifiers.name }}</code></td>
{% else %}
<td></td>
{% endif %}
{% endif %}
<td>
{% if summary.link %}
<a href="{{ summary.link }}"><code>{{ final_name.name | e}}</code></a>
{% else %}
<a href="#{{ final_name.name | lower() | replace(' ', '-') | e }}"><code>{{ final_name.name | e}}</code></a>
{% endif %}
</td>
<td>{{ desc }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% if inherits %}
{% for classpath, inherit in inherits.items() %}
<table>
<thead>
<tr>
<th>Methods inherited from <a href="{{ inherit.link }}"><code>{{ classpath }}</code></a></th>
</tr>
</thead>
<tbody>
<tr>
<td>
{% for inh in inherit.list %}
<a href="{{ inherit.link }}#{{ inh | lower() | replace(' ', '-') | e }}">
<code>{{- inh | e -}}</code>
</a>
{%- if loop.revindex > 1 %}, {% endif -%}
{% endfor %}
</td>
</tr>
</tbody>
</table>
{% endfor %}
{% endif %}
</div>
</div>
{%- endmacro %}
{% macro info(title, details, details_type = 'unknown', defaults = []) -%}
<h2 id="{{ title | lower() | replace(' ', '-') | e}}">
{{ title }}
</h2>
{% for detail in details %}
<div class="api-detail">
{% set final_name = namespace(name = "") %}
{% if details_type == "enum" %}
{% set final_name.name = detail.name | upper() %}
{% else %}
{% set final_name.name = detail.name ~ "(" %}
{% endif %}
{% for parameter in detail.parameters %}
{% set final_name.name = final_name.name ~ parameter.type %}
{% if loop.revindex > 1 %}{% set final_name.name = final_name.name ~ ", " %}{% endif %}
{% endfor %}
{% if details_type != "enum" %}
{% set final_name.name = final_name.name ~ ")" %}
{% endif %}
<h3 id="{{ final_name.name | lower() | replace(' ', '-') | e }}">
{% if detail.deprecated %}
<api__deprecated></api__deprecated>
{% endif %}
{% for attribute in detail.attributes %}
<api__{{- attribute -}}></api__{{- attribute -}}>
{% endfor %}
{% if enum %}
<api__static></api__static> <api__final></api__final>
{% endif %}
{% if detail.type and details_type == "method" %}
{% if detail.type.link %}
<a href="{{ detail.type.link }}" class="api-type__{{- detail.type.type | default('primitive') -}}">{{ detail.type.name | default('void') | e }}</a>
{% else %}
<span class="api-type__{{- detail.type.type | default('primitive') -}}">{{ detail.type.name | default('void') | e }}</span>
{% endif %}
{% elif defaults and defaults.type and details_type == "method" %}
{% if defaults.type.link %}
<a href="{{ defaults.type.link }}" class="api-type__{{- defaults.type.type | default('primitive') -}}">{{ defaults.type.name | default('void') | e }}</a>
{% else %}
<span class="api-type__{{- defaults.type.type | default('primitive') -}}">{{ defaults.type.name | default('void') }}</span>
{% endif %}
{% elif details_type == "method" %}
<span class="api-type__primitive">void</span>
{% endif %}
<code>{{ final_name.name | e }}</code><a href="#{{ final_name.name | lower() | replace(' ', '-') | e }}" class="headerlink" title="Permanent link"></a>
</h3>
{% if detail.deprecated %}
<div class="admonition deprecated">
<div class="admonition-title">Deprecation Warning</div>
<p>
{{ detail.deprecated }}
</p>
</div>
{% endif %}
{% if detail.description %}
<p class="api-detail__description">
{{ detail.description }}
</p>
{% endif %}
{% if detail.parameters %}
<h4 id="{{ detail.name | lower() | replace(' ', '-') | e }}-parameters">
Parameters:<a href="#{{ detail.name | lower() | replace(' ', '-') | e }}-parameters" class="headerlink" title="Permanent link"></a>
</h4>
<ul>
{% for parameter in detail.parameters %}
<li>
{% for attribute in parameter.attribute %}
<api__{{- attribute -}}></api__{{- attribute -}}>
{% endfor %}
<code>{{ parameter.type | e }}: {{ parameter.name }}</code>{% if parameter.description %} - {{ parameter.description }}{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
{% if detail.returns %}
<h4 id="{{ detail.name | lower() | replace(' ', '-') | e }}-returns">
Returns:<a href="#{{ detail.name | lower() | replace(' ', '-') | e }}-returns" class="headerlink" title="Permanent link"></a>
</h4>
<p class="api-detail__description">
{{ detail.returns }}
</p>
{% endif %}
{% if detail.throws %}
<h4 id="{{ detail.name | lower() | replace(' ', '-') | e }}-throws">
Throws:<a href="#{{ detail.name | lower() | replace(' ', '-') | e }}-throws" class="headerlink" title="Permanent link"></a>
</h4>
<ul>
{% for throw in detail.throws %}
<li>
<code>{{ throw.name }}</code>{% if throw.description %} - {{ throw.description }}{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
{% if seealso %}
<h4 id="{{ detail.name | lower() | replace(' ', '-') | e }}-see-also">
See also:<a href="#{{ detail.name | lower() | replace(' ', '-') | e }}-see-also" class="headerlink" title="Permanent link"></a>
</h4>
<ul>
{% for see in detail.seealso %}
<li>
<a href="{{ see.link }}"><code>{{ see.name }}</code></a>
</li>
{% endfor %}
</ul>
{% endif %}
</div>
{% endfor %}
{%- endmacro %}
@@ -1,61 +0,0 @@
{% if page.meta.constructors %}
## Constructor Details
{% for constructor in page.meta.constructors %}
<div class="api-detail" markdown>
{% set name = namespace(text = constructor.name ~ "(") %}
{% for parameter in constructor.parameters %}
{% set name.text = name.text ~ parameter.type %}
{% if loop.revindex > 1 %}{% set name.text = name.text ~ ", " %}{% endif %}
{% endfor %}
{% set name.text = name.text ~ ")" %}
{% if constructor.deprecated %}
{% set name.text = "<api__deprecated></api__deprecated> " ~ name.text %}
{% endif %}
{% for attribute in constructor.attributes %}
{% set name.text = "<api__{{- attribute -}}></api__{{- attribute -}}> " ~ name.text %}
{% endfor %}
### {{ name.text | e }} { #{{ constructor.name | lower() | replace(' ', '-') | e }} }
{% if constructor.deprecated %}
/// deprecated | Deprecation Warning
{{ constructor.deprecated }}
///
{% endif %}
{% if constructor.description %}
<p class="api-detail__description">
{{ constructor.description }}
</p>
{% endif %}
{% if constructor.parameters %}
#### Parameters: { #{{ constructor.name | lower() | replace(' ', '-') | e }}-parameters }
{% for parameter in constructor.parameters %}
- {% for attribute in parameter.attributes %}<api__{{- attribute -}}></api__{{- attribute -}}>{% endfor %} `{{ parameter.type | e}}: {{ parameter.name }}`{% if parameter.description %} - {{ parameter.description }}{% endif %}
{% endfor %}
{% endif %}
{% if constructor.seealso %}
#### See also: { #{{ constructor.name | lower() | replace(' ', '-') | e }}-see-also }
<div class="api-detail__description" markdown>
{% for see in constructor.seealso %}
- [`{{ see.name }}`]({{ see.link }})
{% endfor %}
</div>
{% endif %}
</div>
{% endfor %}
{% endif %}
@@ -1,14 +0,0 @@
{% if page.meta.constructors %}
## Constructor Summary
| Constructor | Description |
|-------------|-------------|{% for constructor in page.meta.constructors %}
| [`{{ constructor.name }}({% for parameter in constructor.parameters -%}
{{ parameter.type }}{% if loop.revindex > 1 %}, {% endif %}
{%- endfor %})`](#{{ constructor.name | lower() }}) | {% if constructor.deprecated -%}
<api__deprecated></api__deprecated> {{ constructor.deprecated.split("\n")[0] }}
{%- elif constructor.description -%}
{{ constructor.description.split("\n")[0] }}
{%- endif %} |
{%- endfor %}
{% endif %}
-32
View File
@@ -1,32 +0,0 @@
{% if page.meta.enums %}
## Enum Constant Details
{% for enum in page.meta.enums %}
<div class="api-detail" markdown>
{% set modifiers = namespace(text = "<api__static></api__static> <api__final></api__final> ") %}
{% if enum.deprecated %}
{% set modifiers.text = "<api__deprecated></api__deprecated> " ~ modifiers.text %}
{% endif %}
### {{ modifiers.text }} `{{ enum.name | upper() }}` { #{{ enum.name | lower() | replace(' ', '-') | e }} }
{% if enum.deprecated %}
/// deprecated | Deprecation Warning
{{ enum.deprecated }}
///
{% endif %}
{% if enum.description %}
<div class="api-detail__description" markdown>
{{ enum.description }}
</div>
{% endif %}
</div>
{% endfor %}
{% endif %}
@@ -1,8 +0,0 @@
{% if page.meta.enums %}
## Enum Constant Summary
| Enum | Description |
|------|-------------|{% for enum in page.meta.enums %}
| [`{{ enum.name | upper() }}`](#{{ enum.name | lower() }}) | {{ enum.description.split("\n")[0] }} |
{%- endfor %}
{% endif %}
-3
View File
@@ -1,3 +0,0 @@
{% macro render_name(method) -%}
{{ method.name }}({% for parameter in method.parameters %}{{ parameter.type }}{% if loop.revindex > 1 %}, {% endif %}{% endfor %})
{%- endmacro %}
-109
View File
@@ -1,109 +0,0 @@
{% if page.meta.methods %}
## Method Details
{% for method in page.meta.methods %}
<div class="api-detail" markdown>
{% set modifiers = namespace(text = "") %}
{% if method.deprecated %}
{% set modifiers.text = "<api__deprecated></api__deprecated> " %}
{% endif %}
{% for attribute in method.attributes %}
{% set modifiers.text = modifiers.text ~ "<api__" ~ attribute ~ "></api__" ~ attribute ~ ">" %}
{% if loop.revindex > 0 %}{% set modifiers.text = modifiers.text ~ " " %}{% endif %}
{% endfor %}
{% if modifiers.text %}{% set modifiers.text = modifiers.text ~ " " %}{% endif %}
{% set method_type = "" %}
{% if method.type %}
{% if method.type.link %}
{% set method_type = '[' ~ method.type.name|default('void')|e ~ '](' ~ method.type.link ~ '){ .api-type__' ~ method.type.type|default('primitive') ~ '} ' %}
{% else %}
{% set method_type = '<span class="api-type__' ~ method.type.type|default('primitive') ~ '">' ~ method.type.name|default('void')|e ~ '</span> ' %}
{% endif %}
{% elif page.meta.defaults and page.meta.defaults.type %}
{% if page.meta.defaults.type.link %}
{% set method_type = '[' ~ page.meta.defaults.type.name|default('void')|e ~ '](' ~ page.meta.defaults.type.link ~ '){ .api-type__' ~ page.meta.defaults.type.type|default('primitive') ~ '} ' %}
{% else %}
{% set method_type = '<span class="api-type__' ~ page.meta.defaults.type.type|default('primitive') ~ '">' ~ page.meta.defaults.type.name|default('void')|e ~ '</span> ' %}
{% endif %}
{% else %}
{% set method_type = '<span class="api-type__primitive">void</span> ' %}
{% endif %}
{% set name = namespace(text = method.name ~ "(") %}
{% for parameter in method.parameters %}
{% set name.text = name.text ~ parameter.type %}
{% if loop.revindex > 1 %}{% set name.text = name.text ~ ", " %}{% endif %}
{% endfor %}
{% set name.text = name.text ~ ")" %}
### {{ modifiers.text }} {{ method_type }} `{{ name.text }}` { #{{ method.name | lower() | replace(' ', '-') | e }} }
{% if method.deprecated %}
/// deprecated | Deprecation Warning
{{ method.deprecated }}
///
{% endif %}
{% if method.description %}
<div class="api-detail__description" markdown>
{{ method.description }}
</div>
{% endif %}
{% if method.parameters %}
#### Parameters: { #{{ method.name | lower() | replace(' ', '-') | e }}-parameters }
<div class="api-detail__description" markdown>
{% for parameter in method.parameters %}
- {% for attribute in parameter.attributes %}<api__{{- attribute -}}></api__{{- attribute -}}>{% endfor %} `{{ parameter.type | e}}: {{ parameter.name }}`{% if parameter.description %} - {{ parameter.description }}{% endif %}
{% endfor %}
</div>
{% endif %}
{% if method.returns %}
#### Returns: { #{{ method.name | lower() | replace(' ', '-') | e }}-returns }
<div class="api-detail__description" markdown>
{{ method.returns }}
</div>
{% endif %}
{% if method.throws %}
#### Throws: { #{{ method.name | lower() | replace(' ', '-') | e }}-throws }
<div class="api-detail__description" markdown>
{% for throw in method.throws %}
- `{{ throw.name }}`{% if throw.description %} - {{ throw.description }}{% endif %}
{% endfor %}
</div>
{% endif %}
{% if method.seealso %}
#### See also: { #{{ method.name | lower() | replace(' ', '-') | e }}-see-also }
<div class="api-detail__description" markdown>
{% for see in method.seealso %}
- [`{{ see.name }}`]({{ see.link }})
{% endfor %}
</div>
{% endif %}
</div>
{% endfor %}
{% endif %}
@@ -1,31 +0,0 @@
{% if page.meta.methods or page.meta.inherits %}
## Method Summary
{% if page.meta.methods %}
| Modifier and Type | Method | Description |
|-------------------|--------|-------------|{% for method in page.meta.methods %}
| `{% if method.attributes -%}
{% for attribute in method.attributes -%}
{{ attribute }}{% if loop.revindex > 0 %} {% endif %}
{%- endfor %} {%- endif %}{% if method.type and method.type.name -%}
{{ method.type.name }}
{%- elif page.meta.defaults and page.meta.defaults.type and page.meta.defaults.type.name -%}
{{ page.meta.defaults.type.name }}
{%- else -%}void{%- endif %}` | [`{{ method.name }}({% for parameter in method.parameters -%}
{{ parameter.type }}{% if loop.revindex > 1 %}, {% endif %}
{%- endfor %})`](#{{ method.name | lower() }}) | {% if method.deprecated -%}
<api__deprecated></api__deprecated> {{ method.deprecated.split("\n")[0] }}
{%- elif method.description -%}
{{ method.description.split("\n")[0] }}
{%- endif %} |
{%- endfor %}
{% endif %}
{% if page.meta.inherits %}
{% for classpath, inherit in page.meta.inherits.items() %}
| Methods inherited from [`{{ classpath }}`]({{ inherit.link }}) |
|----------------------------------------------------------------|
| {% for entry in inherit.list -%}[`{{ entry.name | e }}`]({{ inherit.link }}#{{ entry.link | lower() | replace(' ', '-') | e }}){% if loop.revindex > 1 %}, {% endif %}{% endfor %} |
{% endfor %}
{% endif %}
{% endif %}
@@ -1,11 +0,0 @@
{% if page.meta.classes %}
## Nested Class Summary
| Modifier and Type | Class | Description |
|-------------------|-------|-------------|{% for class in page.meta.classes %}
| `{% if class.attributes -%}
{% for attribute in class.attributes -%}
{{ attribute }}{% if loop.revindex > 1 %} {% endif %}
{%- endfor %} {%- endif %}{{ class.type }}` | [`{{ class.name }}`]({{ class.link }}) | {{ class.description.split("\n")[0] }} |
{%- endfor %}
{% endif %}
+3 -3
View File
@@ -1,7 +1,7 @@
{% extends "base.html" %}
{% block announce %}
<span class="twemoji balloon">{% include ".icons/material/balloon.svg" %}</span>
<span class="rainbow">Celebrating <b>50,000 downloads</b> of AdvancedServerList! THANK YOU!</span>
<span class="twemoji balloon">{% include ".icons/material/balloon.svg" %}</span>
<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"><b>25,000 downloads!</b></a> Thank you!
<span class="twemoji">{% include ".icons/material/party-popper.svg" %}</span>
{% endblock %}
+1 -1
View File
@@ -1,6 +1,6 @@
[discord]: https://discord.gg/MyPTjpsgGH
[placeholderapi]: https://modrinth.com/plugin/PlaceholderAPI
[placeholderapi]: https://hangar.papermc.io/HelpChat/PlaceholderAPI
[viaversion]: https://hangar.papermc.io/ViaVersion/ViaVersion
[papiproxybridge]: https://hangar.papermc.io/William278/PAPIProxyBridge
[maintenance]: https://hangar.papermc.io/kennytv/Maintenance
@@ -16,7 +16,8 @@ The BanPlugins Addon is available in downloads of AdvancedServerList on [:simple
**Supported Platforms:**
<!-- icon:paper --> <!-- icon:bungee -->
- :fontawesome-solid-paper-plane: Paper
- :simple-spigotmc: BungeeCord
| Placeholder | Description |
|---------------------------------|------------------------------------------------------------------------------------------|
@@ -56,7 +57,9 @@ motd:
**Supported Platforms:**
<!-- icon:all -->
- :fontawesome-solid-paper-plane: Paper
- :simple-spigotmc: BungeeCord
- :fontawesome-solid-paper-plane: Velocity
| Placeholder | Description |
|---------------------------------|------------------------------------------------------------------------------------------|
@@ -92,7 +95,9 @@ motd:
**Supported Platforms:**
<!-- icon:all -->
- :fontawesome-solid-paper-plane: Paper
- :simple-spigotmc: BungeeCord
- :fontawesome-solid-paper-plane: Velocity
| Placeholder | Description |
|------------------------------|------------------------------------------------------------------------------------------|
-39
View File
@@ -1,39 +0,0 @@
---
icon: octicons/checklist-24
---
# LuckPerms Addon
The LuckPerms addon provides placeholders to return values from LuckPerms for a specific player.
This can be a players primary group, whether they have a specific group or whether they have a specific permission.
/// note
The Userdata retrieval is done asynchronously by LuckPerms.
However, this addon forces the thread for this to be blocked, which can cause lag, if done on the main thread.
This was done due to possible downsides and issues with trying to keep it async and block-free.
To mitigate possible lag is the retrieved user data cached for 1 minute before being discarded.
///
## Placeholders
**Supported Platforms:**
<!-- icon:all -->
| Placeholder | Description |
|-------------------------------------------|-----------------------------------------------------------------------------------------------------------------|
| `${luckperms hasGroup <group>}` | Returns whether the player has the specified group or not. |
| `${luckperms hasPermission <permission>}` | Returns whether the player has the specified permission.<br/>No permission or one set to `false` returns false. |
| `${luckperms primaryGroup}` | Gives the name of the primary group the player has. |
/// details | Example
type: example
```yaml title="is_admin.yml
priority: 1
condition: "${luckperms hasGroup admin}"
motd:
- "<yellow>Welcome Admin!"
```
///
+2 -16
View File
@@ -13,30 +13,16 @@ These Addons are made by Andre601 and available together with the plugin downloa
<div class="grid cards" markdown>
- [**BanPlugins**](first-party/banplugins.md)
- [**BanPlugins**](banplugins.md)
----
Addon for different Punishment plugins such as AdvancedBan, that add new placeholders to use in AdvancedServerList.
- [**LuckPerms**](first-party/luckperms.md)
----
Addon for adding LuckPerms-related placeholders to use in conditions.
</div>
## Third-party
Third-party addons made by other people not working on AdvancedServerList.
<div class="grid cards" markdown>
- [**SayanVanish**](third-party/sayanvanish.md)
----
SayanVanish adds additional placeholders to the Velocity version of AdvancedServerList to use.
</div>
*Currently no entries*
-43
View File
@@ -1,43 +0,0 @@
---
icon: octicons/eye-closed-24
---
# SayanVanish
The plugin [SayanVanish]{ target="_blank" rel="nofollow" } is hooking into AdvancedServerList to provide placeholders of its own to be used in conditions and text options.
## Placeholders
/// tab | :fontawesome-solid-paper-plane: Paper
[:octicons-file-code-24: Source][source-paper]
| Placeholder | Description |
|----------------------------------|--------------------------------------------------------------------------------------------|
| `${sayanvanish vanished}` | Returns true or false based on if the player is vanished. |
| `${sayanvanish level}` | Returns the vanish level of the player. |
| `${sayanvanish count}` | Returns the number of online players that are vanished. |
| `${sayanvanish online_here}` | Returns the number of online players on the same server that aren't vanished. |
| `${sayanvanish online_total}` | Returns the number of online players on the entire network that aren't vanished.^[1](#n1)^ |
| `${sayanvanish online_<server>}` | Returns the number of online players on `<server>` that aren't vanished.^[1](#n1)^ |
| `${sayanvanish vanish_prefix}` | Returns the prefix for the player while they are vanished or an empty String. |
| `${sayanvanish vanish_suffix}` | Returns the suffix for the player while they are vanished or an empty String. |
<small>^1^{ #n1 } Only works when Proxy mode is enabled and will otherwise return `PROXY_MODE IS NOT ENABLED!`</small>
///
/// tab | :simple-velocity: Velocity
[:octicons-file-code-24: Source][source-velocity]
| Placeholder | Description |
|----------------------------------|----------------------------------------------------------------------------------|
| `${sayanvanish vanished}` | Returns true or false based on if the player is vanished. |
| `${sayanvanish level}` | Returns the vanish level of the player. |
| `${sayanvanish count}` | Returns the number of online players that are vanished. |
| `${sayanvanish online_total}` | Returns the number of online players on the entire network that aren't vanished. |
| `${sayanvanish online_<server>}` | Returns the number of online players on `<server>` that aren't vanished. |
| `${sayanvanish vanish_prefix}` | Returns the prefix for the player while they are vanished or an empty String. |
| `${sayanvanish vanish_suffix}` | Returns the suffix for the player while they are vanished or an empty String. |
///
[SayanVanish]: https://modrinth.com/plugin/sayanvanish
[source-paper]: https://github.com/Syrent/SayanVanish/blob/6e03b874464a7f07c0baba233c94cdfefab5d3e2/sayanvanish-bukkit/src/main/kotlin/org/sayandev/sayanvanish/bukkit/feature/features/hook/FeatureHookAdvancedServerList.kt
[source-velocity]: https://github.com/Syrent/SayanVanish/blob/6e03b874464a7f07c0baba233c94cdfefab5d3e2/sayanvanish-proxy/sayanvanish-proxy-velocity/src/main/kotlin/org/sayandev/sayanvanish/velocity/feature/features/hook/FeatureHookAdvancedServerList.kt
+2 -2
View File
@@ -3,7 +3,7 @@ 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/options/condition.md) of the plugin.
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).
@@ -101,7 +101,7 @@ public class MyPlugin extends Plugin {
```
///
/// tab | :simple-velocity: Velocity Example
/// tab | :fontawesome-solid-paper-plane: Velocity Example
```java title="MyPlugin.java"
@Plugin(
id = "myplugin",
+14 -14
View File
@@ -14,7 +14,7 @@ They are ordered newest to oldest.
- 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
[hidePlayersHoverEnabled]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#hideplayershoverenabled(nullbool)
## v4.0.0 { #v4.0.0 }
@@ -30,8 +30,8 @@ They are ordered newest to oldest.
- [`onlinePlayersCount(String)`][onlinePlayersCount]
- [`onlinePlayersEnabled(NullBool)`][onlinePlayersEnabled]
[onlinePlayersCount]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#onlineplayerscount
[onlinePlayersEnabled]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#onlineplayersenabled
[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 }
@@ -62,15 +62,15 @@ 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
[extraplayersenabled]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#extraplayersenabled
[favicon]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#favicon
[hideplayersenabled]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#hideplayersenabled
[maxplayerscount]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#maxplayerscount
[maxplayersenabled]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#maxplayersenabled
[motd]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#motd
[playercounttext]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#playercounttext
[players]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#players
[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(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)
[players]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md#players(list<string>)
### :octicons-pencil-24:{ .changelog-changed title="Changed" } Changed { #v3.2.0-changed }
@@ -137,8 +137,8 @@ They are ordered newest to oldest.
- `setExtraPlayerEnabled(Integer)` in `ProfileEntry.Builder` - Typo in method name.
[profileentry]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/index.md
[maxplayersenabled]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/index.md#maxplayersenabled
[maxplayerscount]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/index.md#maxplayerscount
[maxplayersenabled]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/index.md#maxplayersenabled()
[maxplayerscount]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/index.md#maxplayerscount()
[profileentry.builder]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md
+53 -5
View File
@@ -88,10 +88,12 @@ dependencies {
Dedicated pages to explain aspects of the API are available:
/// html | div.grid.cards
<div class="grid cards" markdown>
- [:octicons-plus-circle-24: Adding Placeholders](adding-placeholders.md)
- [:octicons-broadcast-24: Listening for Events](listening-for-events.md)
///
</div>
## Declare AdvancedServerList as (soft)depend
@@ -133,7 +135,7 @@ dependencies:
////
///
/// tab | :simple-spigotmc: BungeeCord
/// tab | :octicons-git-merge-24: BungeeCord
//// tab | Softdepend
```yaml title="bungee.yml"
name: "MyPlugin"
@@ -161,7 +163,7 @@ depends:
////
///
/// tab | :simple-velocity: Velocity
/// tab | :octicons-git-merge-24: Velocity
//// tab | Softdepend (File)
```json title="velocity-plugin.json"
{
@@ -249,4 +251,50 @@ public class MyPlugin {
### 4. You're done!
Your plugin should now hook into AdvancedServerList and register its own custom placeholders to use.
Your plugin should now hook into AdvancedServerList and register its own custom placeholders to use.
## Listening for events
<!-- admo:info These events require the platform-specific APIs -->
AdvancedServerList provides Events that your plugin can listen for specific situations.
### PreServerListSetEvent
This event is called **before** AdvancedServerList starts handling the PingEvent of the Server/Proxy.
It allows you to get and alter the [ProfileEntry][profileentry] used in the PingEvent, or even outright cancel the event itself.
The event offers the following methods:
- [`getEntry()`][getentry] - Gets the currently set [`ProfileEntry`][profileentry].
- [`setEntry(ProfileEntry)`][setentry] - Sets a new [`ProfileEntry`][profileentry] to use.
- [`isCancelled()`][iscancelled] - Gets the current cancelled state of the event.
- [`setCancelled(boolean)`][setcancelled] - Sets the current cancelled state.
Please check the server/proxy's documentation on how to listen for events with your plugin.
[getentry]: reference/api/ch.andre601.advancedserverlist.api/events/genericserverlistevent.md#getentry()
[setentry]: reference/api/ch.andre601.advancedserverlist.api/events/genericserverlistevent.md#setentry(profileentry)
[iscancelled]: reference/api/ch.andre601.advancedserverlist.api/events/genericserverlistevent.md#iscancelled()
[setcancelled]: reference/api/ch.andre601.advancedserverlist.api/events/genericserverlistevent.md#setcancelled(boolean)
### PostServerListSetEvent
This event is called **after** AdvancedServerList has completed its handling of the Server's/Proxy's PingEvent.
It provides a method to retrieve the [ProfileEntry][profileentry] used during the PingEvent.
Do note that this ProfileEntry is **not** a representation of what is actually displayed in the server list, but rather what has been used by AdvancedServerList. It may also be `null` should the event handling get cancelled for any reason.
[profileentry]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/index.md
### ProfileEntry
The ProfileEntry record is the core class used within the `PreServerListSetEvent`. It sets what values (MOTD, Favicon, Players, etc.) should be displayed.
Note that the record is - by nature - immutable and that the [`Builder`][builder] class should be used to create a new ProfileEntry instance to use.
A convenience method ([`builder()`][builder()]) exists to create a `Builder` instance from an existing `ProfileEntry` in case you only want to alter certain options while everything else should remain unchanged.
This allows you to customize the Server list using your plugin. Just keep in mind that other plugins may also do the same, overriding your changes.
[builder]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/builder.md
[builder()]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/index.md#builder()
+14 -119
View File
@@ -7,10 +7,12 @@ These events are structurally the same across all platforms, yet still require t
The plugin currently offers the following events:
/// html | div.grid.cards
<div class="grid cards" markdown>
- [PreServerListSetEvent](#preserverlistsetevent)
- [PostServerListSetEvent](#postserverlistsetevent)
///
</div>
## PreServerListSetEvent
@@ -20,16 +22,12 @@ This event is executed before AdvancedServerList tries to modify the player list
The following methods are available to use:
| Method | Description |
|-----------------------------------------|--------------------------------------------------------------------------------|
| [`getEntry()`][getentry] | Returns the [`ProfileEntry`][profileentry] that AdvancedServerList should use. |
| [`setEntry(ProfileEntry)`][setentry] | Sets the [`ProfileEntry`][profileentry] that should be used. |
| [`isCancelled()`][iscancelled] | Returns wether this event was cancelled or not. |
| [`setCancelled(boolean)`][setcancelled] | Sets the event's cancelled state. |
[setentry]: reference/api/ch.andre601.advancedserverlist.api/events/genericserverlistevent.md#setentry
[iscancelled]: reference/api/ch.andre601.advancedserverlist.api/events/genericserverlistevent.md#iscancelled
[setcancelled]: reference/api/ch.andre601.advancedserverlist.api/events/genericserverlistevent.md#setcancelled
| 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 }
@@ -43,9 +41,7 @@ This event is executed after AdvancedServerList completed the server list modifi
| Method | Description |
|--------------------------|--------------------------------------------------------------------------|
| [`getEntry()`][getentry] | Returns the [`ProfileEntry`][profileentry] that AdvancedServerList used. |
[getentry]: reference/api/ch.andre601.advancedserverlist.api/events/genericserverlistevent.md#getentry
| `getEntry()` | Returns the [`ProfileEntry`][profileentry] that AdvancedServerList used. |
### Notes { #postserverlistsetevent-notes }
@@ -54,107 +50,7 @@ This event is executed after AdvancedServerList completed the server list modifi
## Registering the Events
/// tab | :fontawesome-solid-paper-plane: Paper
```java { title="MyPlugin.java" .annotate }
public class MyPlugin extends JavaPlugin {
@Override
public void onEnable() {
// (1)
Bukkit.getPluginManager().registerEvents(new ASLEventListener(), this);
}
}
```
1. `getServer()` from the `JavaPlugin` class can be used here too instead of the `Bukkit` singleton.
```java { title="ASLEventListener.java" }
public class ASLEventListener implements Listener {
@EventHandler
public void onPreServerListSet(PreServerListSetEvent event) {
System.out.println("PreServerListSetEvent fired!");
}
@EventHandler
public void onPostServerListSetEvent(PostServerListSetEvent event) {
System.out.println("PostServerListSetEvent fired!");
}
}
```
///
/// tab | :simple-spigotmc: BungeeCord
```java { title="MyPlugin.java" }
public class MyPlugin extends Plugin {
@Override
public void onEnable() {
getProxy().getPluginManager().registerListener(this, new ASLEventListener());
}
}
```
```java { title="ASLEventListener.java" }
public class ASLEventListener implements Listener {
@EventHandler
public void onPreServerListSet(PreServerListSetEvent event) {
System.out.println("PreServerListSetEvent fired!");
}
@EventHandler
public void onPostServerListSetEvent(PostServerListSetEvent event) {
System.out.println("PostServerListSetEvent fired!");
}
}
```
///
/// tab | :simple-velocity: Velocity
```java { title="MyPlugin.java" }
@Plugin(
id = "myplugin",
name = "MyPlugin",
version = "1.0.0",
authors = {"author"},
dependencies = {
@Dependency(
id = "advancedserverlist",
)
}
)
public class MyPlugin {
private final ProxyServer proxy;
@Inject
public MyPlugin(ProxyServer server) {
this.proxy = proxy;
}
@Subscribe
public void init(ProxyInitializeEvent event) {
proxy.getEventManager().register(this, new ASLEventListener());
}
}
```
```java { title="ASLEventListener.java" }
public class ASLEventListener {
@Subscribe
public void onPreServerListSet(PreServerListSetEvent event) {
System.out.println("PreServerListSetEvent fired!");
}
@Subscribe
public void onPostServerListSetEvent(PostServerListSetEvent event) {
System.out.println("PostServerListSetEvent fired!");
}
}
```
///
To register an event, please refer to our platforms documentation on how to do this.
## What is a ProfileEntry?
@@ -164,7 +60,6 @@ The `ProfileEntry` is a record, representing the values of a [Server List Profil
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()`][builder] exists to create a Builder instance of the ProfileEntry with its values already pre-set.
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
[builder]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/index.md#builder
[profileentry]: reference/api/ch.andre601.advancedserverlist.api/profiles/profileentry/index.md
@@ -1,10 +1,10 @@
---
api: true
template: api-doc.html
methods:
- name: 'get'
description: |
Retrieve the instance used of this API.
Retrieve the instance used of this API.<br>
If no instance has been made so far will a new one be created.
returns: 'Instance of this API.'
attributes:
@@ -14,38 +14,38 @@ methods:
type: 'object'
- name: 'addPlaceholderProvider'
description: |
Adds the provided [`PlaceholderAPIProvider`](placeholderprovider.md) to the list, if it passes the following checks:
- Provided PlaceholderProvider instance is not null.
- The identifier is not null nor empty.
- The identifier does not contain any spaces.
- A PlaceholderProvider with the same identifier doesn't exist already.
Not passing any of the above checks results in a [`InvalidPlaceholderProviderException`](exceptions/invalidplaceholderproviderexception.md) being thrown.
Adds the provided <a href="./../placeholderprovider/"><code>PlaceholderProvider</code></a> to the list, if it passes the following checks:<br>
<ul>
<li>Provided PlaceholderProvider instance is not null.</li>
<li>The identifier is not null nor empty.</li>
<li>The identifier does not contain any spaces.</li>
<li>A PlaceholderProvider with the same identifier doesn't exist already.</li>
</ul>
Not passing any of the above checks results in a <a href="./../exceptions/invalidplaceholderproviderexception/"><code>InvalidPlaceholderProviderException</code></a> being thrown.
parameters:
- name: 'placeholderProvider'
description: 'The [`PlaceholderProvider`](placeholderprovider.md) to add.'
description: 'The <a href="./../placeholderprovider/"><code>PlaceholderProvider</code></a> to add.'
type: PlaceholderProvider
attributes:
- notnull
throws:
- name: 'InvalidPlaceholderProviderException'
description: 'When the provided [`PlaceholderProvider`](placeholderprovider.md) is null, has a null or empty identifier, the identifier contains spaces, or another PlaceholderProvider with the same identifier is already in use.'
description: 'When the provided <a href="./../placeholderprovider/"><code>PlaceholderProvider</code> instance</a> is null, has a null or empty identifier, the identifier contains spaces, or another PlaceholderProvider with the same identifier is already in use.'
type:
name: 'void'
- name: 'retrievePlaceholderProvider'
description: 'Retrieves the [`PlaceholderProvider`](placeholderprovider.md) associated with the provided identifier, or `null` should no such entry exist.'
description: 'Retrieves the <a href="./../placeholderprovider/"><code>PlaceholderProvider</code></a> associated with the provided identifier, or <code>null</code> should no such entry exist.'
parameters:
- name: 'identifier'
description: 'The identifier to find a matching [`PlaceholderProvider`](placeholderprovider.md) for.'
description: 'The identifier to find a matching <a href="./../placeholderprovider/"><code>PlaceholderProvider</code></a> for.'
type: String
returns: 'Possibly-null [`PlaceholderProvider` instance](placeholderprovider.md).'
returns: 'Possibly-null <a href="./../placeholderprovider/"><code>PlaceholderProvider</code> instance</a>.'
attributes:
- nullable
type:
name: PlaceholderProvider
type: object
link: 'placeholderprovider.md'
link: './../placeholderprovider'
---
# <api__class></api__class> AdvancedServerListAPI
@@ -1,27 +1,27 @@
---
api: true
template: api-doc.html
methods:
- name: 'getEntry'
description: 'Gets the [`ProfileEntry`](../profiles/profileentry/index.md) currently set.'
description: 'Gets the <a href="../../profiles/profileentry/"><code>ProfileEntry</code></a> currently set.'
returns: The currently used ProfileEntry.
type:
name: ProfileEntry
type: object
link: '../profiles/profileentry/index.md'
link: '../../profiles/profileentry/'
- name: 'setEntry'
description: |-
Sets the new [`ProfileEntry`](../profiles/profileentry/index.md) to use.
This may not be `null`.
Sets the new <a href="../../profiles/profileentry/"><code>ProfileEntry</code></a> to use.<br>
This may not be <code>null</code>.
parameters:
- name: 'entry'
description: 'The new [`ProfileEntry`](../profiles/profileentry/index.md) to use.'
description: 'The new <a href="../../profiles/profileentry/"><code>ProfileEntry</code></a> to use.'
type: ProfileEntry
attributes:
- notnull
throws:
- name: 'IllegalArgumentException'
description: 'When the provided ProfileEntry is `null`'
description: 'When the provided ProfileEntry is <code>null</code>'
type:
name: 'void'
- name: 'isCancelled'
@@ -1,13 +1,11 @@
---
api: true
template: 'api-doc.html'
constructors:
- name: 'InvalidPlaceholderProviderException'
description: Basic Constructor to create an instance of this Exception.
parameters:
- name: msg
type: String
description: The reason of the exception.
---
# <api__class></api__class> InvalidPlaceholderProviderException
@@ -1,27 +1,29 @@
---
api: true
template: api-doc.html
methods:
- name: 'getName'
description: |
Returns the name of the player.
On BungeeCord and Velocity is the returned value always what AdvancedServerList has cached, while on paper the value may differ, should the plugin be able to obtain a OfflinePlayer instance from the Server.
Name may also be whatever has been defined in AdvancedServerList's `unknownPlayer -> name` config.yml Option.
Returns the name of the player.<br>
Note that on Spigot, Paper and other forks can the name differ from the one cached by AdvancedServerList, if the plugin was able to retrieve an OfflinePlayer instance from the server. On BungeeCord and Velocity will the returned String always be the name from the cache.<br>
<br>
Name may be whatever has been defined in AdvancedServerList's config.yml, should the player not be cached yet by the plugin.
returns: 'String representing the player''s name'
type:
name: String
type: object
- name: 'getProtocol'
description: Returns the protocol ID the player is using. The protocol ID is a unique positive integer that is used in Minecraft to determine the Version used by the Client and Server.
description: |
Returns the protocol ID the player is using.<br>
The protocol ID is an integer used by Minecraft to determine what version a server or client is running.
returns: 'Integer representing the protocol version of this player.'
type:
name: int
- name: 'getUUID'
description: |
Returns the unique ID associated with this player.
UUID may also be whatever has been defined in AdvancedServerList's `unknownPlayer -> uuid` config.yml Option.
Returns the unique ID associated with this player.<br>
<br>
UUID may be whatever has been defined in AdvancedServerList's config.yml, should the player not be cached yet by AdvancedServerList.
returns: 'UUID of the player'
type:
name: UUID
@@ -30,11 +32,11 @@ methods:
# <api__interface></api__interface> GenericPlayer
Interface used by the API to share common information across all platforms.
A basic interface used to store generic player data to use in placeholder resolving.
The different variants of AdvancedServerList implement this into their own class and may provide additional data not available through this interface.
Platform-specific interfaces extending this interface are available and may offer additional features in addition to what this interface is offering.
This interface always offers data for the following:
The following values will always be present, no matter the platform this interface is used on:
- [Name of the Player](#getname)
- [Protocol version of the Player](#getprotocol)
- [UUID of the Player](#getuuid)
- [Name of the Player](#getname())
- [Protocol version of the Player](#getprotocol())
- [UUID of the Player](#getuuid())
@@ -1,5 +1,5 @@
---
api: true
template: api-doc.html
methods:
- name: 'getPlayersOnline'
@@ -24,11 +24,4 @@ methods:
# <api__interface></api__interface> GenericServer
Interface used to share common information across all platforms.
Platform-specific interfaces extending this interface are available and may offer additional features in addition to what this interface is offering.
This interface always offers data for the following:
- [Players currently online on the Proxy/Server](#getplayersonline)
- [Max Players allowed to join the Proxy/Server](#getplayersmax)
- [IP/Domain pinged by the Player](#gethost)
Simple class used to wrap around some generic server data such as online player count, number of total players that can join and the host (IP/Domain) that got pinged by the player.
@@ -1,23 +1,23 @@
---
api: true
template: api-doc.html
enums:
- name: 'true'
description: 'Boolean value `true`'
description: 'Boolean value <code>true</code>'
- name: 'false'
description: 'Boolean value `false`'
description: 'Boolean value <code>false</code>'
- name: 'not_set'
description: 'Boolean value `null`'
description: 'Boolean value <code>null</code>'
methods:
- name: 'resolve'
description: |
Returns a NullBool instance based on the provided `Boolean` value.
In case of `null` being provided will [`NullBool.NOT_SET`](#not_set) be returned, otherwise will the corresponding NullBool instance matching the Boolean value be returned.
returns: 'NullBool instance based on the provided `Boolean` value.'
Returns a NullBool instance based on the provided <code>Boolean</code> value.<br>
In case of <code>null</code> being provided will <a href="#not_set"><code>NullBool.NOT_SET</code></a> be returned, otherwise will the corresponding NullBool instance matching the Boolean value be returned.
returns: 'NullBool instance based on the provided <code>Boolean</code> value.'
parameters:
- name: 'bool'
description: 'The `Boolean` value to receive a NullBool instance for.'
description: 'The <code>Boolean</code> value to receive a NullBool instance for.'
type: Boolean
attributes:
- nullable
@@ -27,19 +27,19 @@ methods:
name: 'NullBool'
type: 'object'
- name: 'isNotSet'
description: 'Returns whether the NullBool is [`NullBool.NOT_SET`](#not_set)'
returns: 'True if the instance is [`NullBool.NOT_SET`](#not_set), otherwise false.'
description: 'Returns whether the NullBool is <a href="#not_set"><code>NullBool.NOT_SET</code></a>.'
returns: 'True if the instance is <a href="#not_set"><code>NullBool.NOT_SET</code></a>, otherwise false.'
type:
name: 'boolean'
type: 'primitive'
- name: 'getOrDefault'
description: |
Gets the corresponding boolean value associated with the NullBool instance.
In the case of NullBool [not being set](#isnotset) will the provided default value be returned.
Gets the corresponding boolean value associated with the NullBool instance.<br>
In the case of NullBool <a href="#isnotset()">not being set</a> will the provided default value be returned.
returns: 'True or false depending on the NullBool instance and the provided default value.'
parameters:
- name: 'def'
description: 'The default boolean value to return should the NullBool instance be [`NullBool.NOT_SET`](#not_set).'
description: 'The default boolean value to return should the NullBool instance be <a href="#not_set"><code>NullBool.NOT_SET</code></a>.'
type: boolean
type:
name: 'boolean'
@@ -48,4 +48,4 @@ methods:
# <api__enum></api__enum> NullBool
Enum used to return a [`Boolean`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Boolean.html){ target="_blank" rel="noopener" } Value with a fallback, should the boolean be `null`.
Enum used to return a `Boolean` value with a default one should no value be set.
@@ -1,5 +1,5 @@
---
api: true
template: api-doc.html
constructors:
- name: 'PlaceholderProvider'
@@ -8,27 +8,27 @@ constructors:
- name: 'identifier'
description: 'The identifier to use for the placeholder. Cannot be empty.'
type: String
attributes:
attribute:
- notnull
methods:
- name: 'parsePlaceholder'
description: |
Method called by AdvancedServerList's StringReplacer class to replace any appearances of `${<identifier> <placeholder>}` with whatever value a matching PlaceholderProvider may return.
Returning `null` will be treated as an invalid placeholder by the plugin, making it return the placeholder as-is without any changes.
Method called by AdvancedServerList's StringReplacer class to replace any appearances of <code>${&lt;identifier&gt; &lt;placeholder&gt;}</code> with whatever value a matching PlaceholderProvider may return.<br>
<br>
Returning <code>null</code> will be treated as an invalid placeholder by the plugin, making it return the placeholder as-is without any changes.
parameters:
- name: 'placeholder'
type: String
description: 'The part of the Placeholder that comes after the identifier and before the closing curly bracket.'
- name: 'player'
type: GenericPlayer
description: 'The [`GenericPlayer` instance](objects/genericplayer.md) used.'
description: 'The <a href="./../objects/genericplayer/"><code>GenericPlayer</code> instance</a> used.'
- name: 'server'
type: GenericServer
description: 'The [`GenericServer` instance](objects/genericserver.md) used.'
description: 'The <a href="./../objects/genericserver/"><code>GenericServer</code> instance</a> used.'
returns: 'Possibly-parsed or possibly-null String to replace the placeholder with.'
attributes:
attribute:
- nullable
- abstract
type:
@@ -47,4 +47,4 @@ methods:
Abstract class that is used to provide your own Placeholder patterns for AdvancedServerList to parse.
In order for your class to be considered a valid PlaceholderProvider will you need to set the `identifier` to a non-null, non-empty value without having any spaces in it.
Once set, use [`AdvancedServerListAPI#addPlaceholderProvider(PlaceholderProvider)`](advancedserverlistapi.md#addplaceholderprovider) to register your class for AdvancedServerList to use.
Once set, use [`AdvancedServerListAPI#addPlaceholderProvider(PlaceholderProvider)`](advancedserverlistapi.md#addplaceholderprovider(placeholderprovider)) to register your class for AdvancedServerList to use.
@@ -1,5 +1,5 @@
---
api: true
template: api-doc.html
defaults:
type:
@@ -13,199 +13,199 @@ constructors:
methods:
- name: 'extraPlayersCount'
description: |
Sets the value to resolve into a number by AdvancedServerList.
Strings not resolving into valid numbers will be treated as this option not being set.
This option has no effect when [`extraPlayersEnabled()`](index.md#extraplayersenabled) is set to [`NullBool.FALSE`](../../objects/nullbool.md#false) or [`NullBool.NOT_SET`](../../objects/nullbool.md#not_set).
Set this to `null` to not alter the max player count. Alternatively [disable extra Players](#extraplayersenabled).
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 <a href="#setextraplayersenabled(nullbool)"><code>disable extra Players</code></a>.
parameters:
- name: 'extraPlayersCount'
description: 'The number of extra players to add.'
type: 'String'
attributes:
attribute:
- 'nullable'
returns: 'This Builder after the extra player count has been set. Useful for chaining.'
- name: 'extraPlayersEnabled'
description: |
Sets whether the extra players feature should be enabled or not.
Set to [`NullBool.NOT_SET`](../../objects/nullbool.md#not_set) to not set this.
Sets whether the extra players feature should be enabled or not.<br>
<br>
Set to <a href="../../../objects/nullbool#not_set"><code>NullBool.NOT_SET</code></a> to not set this.
parameters:
- name: 'extraPlayersEnabled'
description: 'Whether the extra players feature should be enabled or not.'
type: 'NullBool'
attributes:
attribute:
- 'notnull'
returns: 'This Builder after the NullBool has been set. Useful for chaining.'
throws:
- name: 'IllegalArgumentException'
description: Thrown by the `CheckUtil` in case `null` has been provided as parameter.
description: Thrown by the <code>CheckUtil</code> in case <code>null</code> has been provided as parameter.
- name: 'favicon'
description: |
Sets the value to use for the favicon.
Sets the value to use for the favicon.<br>
The following values are supported:
- URL to a valid PNG file
- File name (with `.png` extension) matching a file saved in the favicons folder of AdvancedServerList
- `${player uuid}` to display the avatar of the player
Set to an empty String or `null` to not alter the Favicon.
<ul>
<li>URL to a valid PNG file.</li>
<li>File name (with <code>.png</code> extension) matching a file saved in the favicons folder of AdvancedServerList.</li>
<li><code>${player uuid}</code> to display the avatar of the player.
</ul>
Set to an empty String or <code>null</code> to not alter the Favicon.
parameters:
- name: 'favicon'
description: 'The favicon to set.'
type: 'String'
attributes:
attribute:
- 'nullable'
returns: 'This Builder after the Favicon has been set. Useful for chaining.'
- name: 'hidePlayersEnabled'
description: |
Sets whether the player count should be hidden or not.
Set to [`NullBool.NOT_SET`](../../objects/nullbool.md#not_set) to not set this.
Sets whether the player count 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: 'hidePlayersEnabled'
description: 'Whether the player count should be hidden or not.'
type: 'NullBool'
attributes:
attribute:
- 'notnull'
returns: 'This Builder after the NullBool has been set. Useful for chaining.'
throws:
- name: 'IllegalArgumentException'
description: 'Thrown by the `CheckUtil` in case `null` has been provided as parameter.'
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.
Set to [`NullBool.NOT_SET`](../../objects/nullbool.md#not_set) to not set this.
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'
attributes:
attribute:
- 'notnull'
returns: 'This Builder after the NullBool has been set. Useful for chaining.'
throws:
- name: 'IllegalArgumentException'
description: 'Thrown by the `CheckUtil` in case `null` has been provided as parameter.'
description: 'Thrown by the <code>CheckUtil</code> in case <code>null</code> has been provided as parameter.'
- name: 'maxPlayersCount'
description: |
Sets the value to resolve into a number by AdvancedServerList.
Strings not resolving into valid numbers will be treated as this option not being set.
This option has no effect when [`maxPlayersEnabled(NullBool)`](#maxplayersenabled) is set to [`NullBool.FALSE`](../../objects/nullbool.md#false) or [`NullBool.NOT_SET`](../../objects/nullbool.md#not_set).
Set this to `null` to not alter the max player count. Alternatively [disable max Players](#maxplayersenabled).
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>.
parameters:
- name: 'maxPlayersCount'
description: 'The number of max players to set.'
type: 'Integer'
attributes:
attribute:
- 'nullable'
returns: 'This Builder after the max player count has been set. Useful for chaining.'
- name: 'maxPlayersEnabled'
description: |
Sets whether the max players feature should be enabled.
Set to [`NullBool.NOT_SET`](../../objects/nullbool.md#not_set) to not set this.
An `IllegalArgumentException` may be thrown by the `CheckUtil` should maxPlayersEnabled be null.
Sets whether the max players feature should be enabled.<br>
<br>
Set to <a href="../../../objects/nullbool/#not_set"><code>NullBool.NOT_SET</code></a> to not set this.<br>
<br>
An <code>IllegalArgumentException</code> may be thrown by the <code>CheckUtil</code> should maxPlayersEnabled be null.
parameters:
- name: 'maxPlayersEnabled'
description: 'Whether the extra players feature should be enabled or not.'
type: 'NullBool'
attributes:
attribute:
- 'notnull'
returns: 'This Builder after the NullBool has been set. Useful for chaining.'
throws:
- name: 'IllegalArgumentException'
description: 'Thrown by the `CheckUtil` in case `null` has been provided as parameter.'
description: 'Thrown by the <code>CheckUtil</code> in case <code>null</code> has been provided as parameter.'
- name: 'motd'
description: |
Sets a new MOTD to use.
Set to an empty list to not change the MOTD.
Only the first two entries of the list will be considered and any additional ones discarded.
An `IllegalArgumentException` may be thrown by the `CheckUtil` should the provided motd list be null.
Sets a new MOTD to use.<br>
<br>
Set to an empty list to not change the MOTD.<br>
Only the first two entries of the list will be considered and any additional ones discarded.<br>
<br>
An <code>IllegalArgumentException</code> may be thrown by the <code>CheckUtil</code> should the provided motd list be null.
parameters:
- name: 'motd'
description: 'The MOTD to use.'
type: 'List<String>'
attributes:
attribute:
- 'notnull'
returns: 'This Builder after the motd has been set. Useful for chaining.'
throws:
- name: 'IllegalArgumentException'
description: 'Thrown by the `CheckUtil` in case `null` has been provided as parameter.'
description: 'Thrown by the <code>CheckUtil</code> in case <code>null</code> has been provided as parameter.'
- name: 'onlinePlayersCount'
description: |
Sets the value to resolve into a number by AdvancedServerList.
Strings not resolving into valid numbers will be treated as this option not being set.
This option has no effect when [`onlinePlayersEnabled(NullBool)`](#onlineplayersenabled) is set to [`NullBool.FALSE`](../../objects/nullbool.md#false) or [`NullBool.NOT_SET`](../../objects/nullbool.md#not_set).
Set this to `null` to not alter the online player count. Alternatively [disable online Players.](#onlineplayersenabled).
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="#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.'
type: 'String'
attributes:
attribute:
- 'nullable'
returns: 'This Builder after the max player count has been set. Useful for chaining.'
- name: 'onlinePlayersEnabled'
description: |
Sets whether the online players feature should be enabled.
Set to [`NullBool.NOT_SET`](../../objects/nullbool.md#not_set) to not set this.
An `IllegalArgumentException` may be thrown by the `CheckUtil` should onlinePlayersEnabled be null.
Sets whether the online players feature should be enabled.<br>
<br>
Set to <a href="../../../objects/nullbool/#not_set"><code>NullBool.NOT_SET</code></a> to not set this.<br>
<br>
An <code>IllegalArgumentException</code> may be thrown by the <code>CheckUtil</code> should onlinePlayersEnabled be null.
parameters:
- name: 'onlinePlayersEnabled'
description: 'Whether the extra players feature should be enabled or not.'
type: 'NullBool'
attributes:
attribute:
- 'notnull'
returns: 'This Builder after the NullBool has been set. Useful for chaining.'
throws:
- name: 'IllegalArgumentException'
description: 'Thrown by the `CheckUtil` in case `null` has been provided as parameter.'
description: 'Thrown by the <code>CheckUtil</code> in case <code>null</code> has been provided as parameter.'
- name: 'playerCountText'
description: |
Sets the text to override the player count with.<br>
<br>
Set to an empty String or `null` to not alter the player count text.
Set to an empty String or <code>null</code> to not alter the player count text.
parameters:
- name: 'playerCountText'
description: 'The text to show in the player count.'
type: 'String'
attributes:
attribute:
- 'nullable'
returns: 'This Builder after the player count text has been set. Useful for chaining.'
- name: 'players'
description: |
Sets the players (lines) to use for the hover.
Sets the players (lines) to use for the hover.<br>
<br>
Set to an empty list to not change the hover text.
An `IllegalArgumentException` may be thrown by the `CheckUtil` should the provided okayersmotd list be null.
<br>
An <code>IllegalArgumentException</code> may be thrown by the <code>CheckUtil</code> should the provided okayersmotd list be null.
parameters:
- name: 'players'
description: 'The lines to set for the hover.'
type: 'List<String>'
attributes:
attribute:
- 'notnull'
returns: 'This Builder after the players have been set. Useful for chaining.'
throws:
- name: 'IllegalArgumentException'
description: 'Thrown by the `CheckUtil` in case `null` has been provided as parameter.'
description: 'Thrown by the <code>CheckUtil</code> in case <code>null</code> has been provided as parameter.'
- name: 'build'
description: 'Creates a new [`ProfileEntry` instance](index.md) with the values set in this Builder.'
returns: 'New [`ProfileEntry` instance](index.md).'
description: 'Creates a new <a href="./.."><code>ProfileEntry</code> instance</a> with the values set in this Builder.'
returns: 'New <a href="./.."><code>ProfileEntry</code> instance</a>.'
type:
name: 'ProfileEntry'
type: 'object'
link: 'index.md'
link: './..'
---
# <api__class></api__class> Builder
@@ -1,11 +1,11 @@
---
api: true
template: api-doc.html
constructors:
- name: 'ProfileEntry'
description: |
Creates a new instance of a ProfileEntry with the given values.
It's recommended to use the [`Builder` class](builder.md) for a more convenient configuration of the Settings.
Creates a new instance of a ProfileEntry with the given values.<br>
It's recommended to use the <a href="./builder/"><code>Builder</code> class</a> for a more convenient configuration of the Settings.
parameters:
- name: 'motd'
description: 'The MOTD to use.'
@@ -64,31 +64,32 @@ constructors:
- nullable
seealso:
- name: 'ProfileEntry.Builder'
link: 'builder.md'
link: './builder/'
classes:
- name: 'Builder'
description: 'Builder class to create a new ProfileEntry instance.'
type: 'static class'
link: 'builder.md'
link: 'builder/'
methods:
- name: 'empty'
description: |
Creates an "empty" PlayerEntry with the following values set:
- [`motd`](#motd): Empty List
- [`players`](#players): Empty List
- [`playerCountText`](#playercounttext): Empty String
- [`favicon`](#favicon): Empty String
- [`hidePlayersEnabled`](#hideplayersenabled): [`NullBool.NOT_SET`](../../objects/nullbool.md#not_set)
- [`extraPlayersEnabled`](#extraplayersenabled): [`NullBool.NOT_SET`](../../objects/nullbool.md#not_set)
- [`maxPlayersEnabled`](#maxplayersenabled): [`NullBool.NOT_SET`](../../objects/nullbool.md#not_set)
- [`onlinePlayersEnabled`](#onlineplayersenabled): [`NullBool.NOT_SET`](../../objects/nullbool.md#not_set)
- [`hidePlayersHoverEnabled`](#hideplayershoverenabled): [`NullBool.NOT_SET`](../../objects/nullbool.md#not_set)
- [`extraPlayersCount`](#extraplayerscount): `null`
- [`maxPlayersCount`](#maxplayerscount): `null`
- [`onlinePlayersCount`](#onlineplayerscount): `null`
Creates an "empty" PlayerEntry with the following values set:<br>
<ul>
<li><a href="#motd()"><code>motd</code></a>: Empty List</li>
<li><a href="#players()"><code>players</code></a>: Empty List</li>
<li><a href="#playercounttext()"><code>playerCountText</code></a>: Empty String</li>
<li><a href="#favicon()"><code>favicon</code></a>: Empty String</li>
<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:
- static
@@ -97,32 +98,26 @@ methods:
type: 'object'
- name: 'copy'
description: |
Creates a copy of this ProfileEntry instance.
This is a convenience method that avoids using [`builder()`](#builder) first followed by [`ProfileEntry.Builder.build()`](builder.md#build):
```java
ProfileEntry entry = ProfileEntry.empty(); // Obtain ProfileEntry
// Both do the same
ProfileEntry newEntry1 = entry.builder().build();
ProfileEntry newEntry2 = entry.copy();
```
Creates a copy of this ProfileEntry instance.<br>
<br>
This is simply a convenience method to not have to call <a href="#builder()"><code>builder()</code></a><code>.</code><a href="./builder/#build()"><code>build()</code></a>.<br>
If you want to modify the ProfileEntry <a href="#builder()"><code>builder()</code></a> is preferred.
returns: 'A copy of this ProfileEntry instance.'
seealso:
- name: 'builder()'
link: '#builder'
link: '#builder()'
type:
name: 'ProfileEntry'
type: 'object'
- name: 'builder'
description: |
Creates a [`Builder` instance](builder.md) with the values from this ProfileEntry set.
Creates a <a href="./builder/"><code>Builder</code> instance</a> with the values from this ProfileEntry set.<br>
Use this method if you would like to modify the ProfileEntry.
returns: 'A new [`Builder` instance](builder.md) with the values of this ProfileEntry set.'
returns: 'A new <a href="./builder/"><code>Builder</code> instance</a> with the values of this ProfileEntry set.'
type:
name: 'Builder'
type: 'object'
link: 'builder.md'
link: './builder/'
- name: 'motd'
description: 'Gets the currently set MOTD of this ProfileEntry.'
returns: 'The current MOTD used by this ProfileEntry.'
@@ -143,63 +138,64 @@ methods:
type: 'object'
- name: 'favicon'
description: |
Gets the currently set favicon of this ProfileEntry.
Gets the currently set favicon of this ProfileEntry.<br>
Note that the favicon usually is and supports one of the following options:
- URL to a valid PNG file
- File name (with `.png` extension) matching a file saved in the favicons folder of AdvancedServerList
- `${player uuid}` to display the avatar of the player
<ul>
<li>URL to a valid PNG file</li>
<li>File name (with <code>.png</code> extension) matching a file saved in the favicons folder of AdvancedServerList</li>
<li><code>${player uuid}</code> to display the avatar of the player</li>
</ul>
returns: 'The current favicon used by this ProfileEntry.'
type:
name: 'String'
type: 'object'
- name: 'hidePlayersEnabled'
description: |
Whether the player count should be hidden or not in this ProfileEntry.
To get the actual boolean value, append [`getOrDefault(boolean)`](../../objects/nullbool.md#getordefault).
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(boolean)"><code>getOrDefault(boolean)</code></a>.
returns: 'Whether the player count should be hidden or not in this ProfileEntry.'
type:
name: 'NullBool'
type: 'object'
link: '../../objects/nullbool.md'
link: '../../objects/nullbool/'
- name: 'extraPlayersEnabled'
description: |
Whether the extra players feature should be used or not in this ProfileEntry.
To get the actual boolean value, append [`getOrDefault(boolean)`](../../objects/nullbool.md#getordefault).
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(boolean)"><code>getOrDefault(boolean)</code></a>.
returns: 'Whether the extra players feature should be used or not in this ProfileEntry.'
type:
name: 'NullBool'
type: 'object'
link: '../../objects/nullbool.md'
link: '../../objects/nullbool/'
- name: 'maxPlayersEnabled'
description: |
Whether the max players feature should be used or not.
To get the actual boolean value, append [`getOrDefault(boolean)`](../../objects/nullbool.md#getordefault).
Whether the max 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 max players feature should be used or not.'
type:
name: 'NullBool'
type: 'object'
link: '../../objects/nullbool.md'
link: '../../objects/nullbool/'
- name: 'onlinePlayersEnabled'
description: |
Whether the online players feature should be used or not.
To get the actual boolean value, append [`getOrDefault(boolean)`](../../objects/nullbool.md#getordefault).
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.md'
link: '../../objects/nullbool/'
- name: 'hidePlayersHoverEnabled'
description: |
Whether the Hover List of online players should be hidden or not.
To get the actual boolean value, append [`getOrDefault(boolean)`](../../objects/nullbool.md#getordefault).
When true, this option will force the [`players()`](#players) list to be ignored.
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.md'
link: '../../objects/nullbool/'
- name: 'extraPlayersCount'
description: 'Gets the currently set number of extra players of this ProfileEntry.'
returns: 'The current number of extra players used by this ProfileEntry.'
@@ -226,15 +222,15 @@ methods:
type: 'object'
- name: 'isInvalid'
description: |
Whether this ProfileEntry is considered invalid or not.
Whether this ProfileEntry is considered invalid or not.<br>
The ProfileEntry is considered invalid if all the following is true:
- [`motd()`](#motd) is empty.
- [`players()`](#players) is empty.
- [`playerCountText()`](#playercounttext) is null/empty **and** [`hidePlayersEnabled()`](#hideplayersenabled) is `false`.
- [`favicon()`](#favicon) is null/empty.
As long as one of the above is **not** true is this ProfileEntry considered valid and `false` will be returned.
<ul>
<li><a href="#motd()"><code>motd()</code></a> is empty.</li>
<li><a href="#players()"><code>players()</code></a> is empty.</li>
<li><a href="#playercounttext()"><code>playerCountText()</code></a> is null/empty <b>and</b> <a href="#hideplayersenabled()"><code>hidePlayersEnabled()</code></a> is <code>false</code>.</li>
<li><a href="#favicon()"><code>favicon()</code></a> is null/empty.</li>
</ul>
As long as one of the above is <b>not</b> true is this ProfileEntry considered valid and <code>false</code> will be returned.
returns: 'Whether this ProfileEntry is invalid or not.'
type:
name: 'boolean'
@@ -246,4 +242,4 @@ methods:
This record represents the content found in a server list profile YAML file.
The content may come from either the "profiles" list, the options in the file itself (global options) or a mix of both.
This class is immutable. Use [`builder()`](#builder) to get a [`Builder` instance](builder.md) with the values of this class added.
This class is immutable. Use [`builder()`](#builder()) to get a [`Builder` instance](builder.md) with the values of this class added.
@@ -1,20 +1,20 @@
---
api: true
template: api-doc.html
constructors:
- name: 'PostServerListSetEvent'
description: 'Constructor for creating a new PostServerListSetEvent instance.'
parameters:
- name: 'entry'
description: 'The [`ProfileEntry`](../../../api/ch.andre601.advancedserverlist.api/profiles/profileentry/index.md) used during the PingEvent.'
description: 'The <a href="../../../../api/ch.andre601.advancedserverlist.api/profiles/profileentry/"><code>ProfileEntry</code></a> used during the PingEvent.'
type: ProfileEntry
attribute:
- nullable
methods:
- name: 'getHandlerList'
description: 'Static [`HandlerList`](https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/HandlerList.html){ target="_blank" rel="nofollow" } getter. Required by Paper.'
returns: 'The static [`HandlerList` instance](https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/HandlerList.html){ target="_blank" rel="nofollow" } of this event.'
description: 'Static <a href="https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/HandlerList.html" target="_blank" rel="nofollow"><code>HandlerList</code></a> getter. Required by Paper.'
returns: 'The static <a href="https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/HandlerList.html" target="_blank" rel="nofollow"><code>HandlerList instance</code></a> of this event.'
attributes:
- static
type:
@@ -22,9 +22,9 @@ methods:
type: 'object'
- name: 'getEntry'
description: |
The [`ProfileEntry`](../../../api/ch.andre601.advancedserverlist.api/profiles/profileentry/index.md) that was used during the PingEvent handling.
The returned value may be `null` should the PingEvent handling be cancelled at any point (i.e. due to an invalid protocol or because the [`PreServerListSetEvent`](preserverlistsetevent.md) has been cancelled).
The <a href="../../../../api/ch.andre601.advancedserverlist.api/profiles/profileentry/"><code>ProfileEntry</code></a> that was used during the PingEvent handling.<br>
<br>
The returned value may be <code>null</code> should the PingEvent handling be cancelled at any point (i.e. due to an invalid protocol or because the <a href="../preserverlistsetevent/"><code>PreServerListSetEvent</code></a> has been cancelled).<br>
The values within the ProfileEntry also do not represent what is actually used within the Server list itself, but only the values provided for the PingEvent handling.
returns: 'Possibly-null ProfileEntry used for the PingEvent.'
attributes:
@@ -32,19 +32,18 @@ methods:
type:
name: 'ProfileEntry'
type: 'object'
link: '../../../api/ch.andre601.advancedserverlist.api/profiles/profileentry/index.md'
link: '../../../../api/ch.andre601.advancedserverlist.api/profiles/profileentry/'
inherits:
'org.bukkit.event.Event':
link: 'https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/Event.html'
list:
- name: 'getHandlers()'
link: 'getHandlers()'
- 'getHandlers()'
---
# <api__class></api__class> PostServerListSetEvent
Called **after** the plugin has updated the Server List.
The provided [`ProfileEntry`](#getentry) may be null due to the PingEvent handling being cancelled (i.e. due to a invalid protocol or due to the [`PreServerListSetEvent`](preserverlistsetevent.md) being cancelled).
The provided [`ProfileEntry`](#getentry()) may be null due to the PingEvent handling being cancelled (i.e. due to a invalid protocol or due to the [`PreServerListSetEvent`](preserverlistsetevent.md) being cancelled).
The entry also only represents the values used during the PingEvent, not the actual content that is being displayed.
@@ -1,5 +1,5 @@
---
api: true
template: api-doc.html
constructors:
- name: 'PreServerListSetEvent'
@@ -9,7 +9,7 @@ constructors:
methods:
- name: 'getHandlerList'
returns: 'Static `HandlerList` instance.'
returns: 'Static <code>HandlerList</code> instance.'
attributes:
- static
type:
@@ -18,24 +18,19 @@ methods:
inherits:
'ch.andre601.advancedserverlist.api.events.GenericServerListEvent':
link: '../../../api/ch.andre601.advancedserverlist.api/events/genericserverlistevent.md'
link: '../../../../api/ch.andre601.advancedserverlist.api/events/genericserverlistevent/'
list:
- name: 'getEntry()'
link: 'getentry'
- name: 'setEntry(ProfileEntry)'
link: 'setentry'
- name: 'isCancelled()'
link: 'iscancelled'
- name: 'setCancelled(boolean)'
link: 'setcancelled'
- 'getEntry()'
- 'setEntry(ProfileEntry)'
- 'isCancelled()'
- 'setCancelled(boolean)'
'org.bukkit.event.Event':
link: 'https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/Event.html'
list:
- name: 'getHandlers()'
link: 'getHandlers()'
- 'getHandlers()'
---
# <api__class></api__class> PreServerListSetEvent
Called **before** AdvancedServerList modifies the server list.
The provided [`ProfileEntry`](../../../api/ch.andre601.advancedserverlist.api/events/genericserverlistevent.md#getentry) will be the one used for the server list.
The provided [`ProfileEntry`](../../../api/ch.andre601.advancedserverlist.api/events/genericserverlistevent.md#getentry()) will be the one used for the server list.
@@ -1,5 +1,5 @@
---
api: true
template: api-doc.html
methods:
- name: 'getPlayer'
@@ -10,24 +10,24 @@ methods:
type: 'object'
- name: 'hasPlayedBefore'
description: |
Returns whether this player has played on the server before.
Returns whether this player has played on the server before.<br>
<br>
The returned boolean may be inaccurate if AdvancedServerList was unable to obtain a valid OfflinePlayer instance.
returns: 'Boolean indicating whether this player has played on the server before.'
type:
name: 'boolean'
- name: 'isBanned'
description: |
Returns whether this player has been banned from the server.
Returns whether this player has been banned from the server.<br>
<br>
The returned boolean may be inaccurate if AdvancedServerList was unable to obtain a valid OfflinePlayer instance.
returns: 'Boolean indicating whether this player has been banned from the server.'
type:
name: 'boolean'
- name: 'isWhitelisted'
description: |
Returns whether this player is whitelisted on the server.
Returns whether this player is whitelisted on the server.<br>
<br>
The returned boolean may be inaccurate if AdvancedServerList was unable to obtain a valid OfflinePlayer instance.
returns: 'Boolean indicating whether this player is whitelisted on the server.'
type:
@@ -35,19 +35,16 @@ methods:
inherits:
'ch.andre601.advancedserverlist.api.objects.GenericPlayer':
link: '../../../api/ch.andre601.advancedserverlist.api/objects/genericplayer.md'
link: '../../../../api/ch.andre601.advancedserverlist.api/objects/genericplayer/'
list:
- name: 'getName()'
link: 'getname'
- name: 'getProtocol()'
link: 'getprotocol'
- name: 'getUUID()'
link: 'getuuid'
- 'getName()'
- 'getProtocol()'
- 'getUUID()'
---
# <api__interface></api__interface> BukkitPlayer
[`GenericPlayer` instance](../../../api/ch.andre601.advancedserverlist.api/objects/genericplayer.md) for the SpigotMC/Paper server implementation of AdvancedServerList.
This interface includes a [`OfflinePlayer` instance](#getplayer) obtained from the server the plugin runs on alongside some getters to get if the player [has played on the server before](#hasplayedbefore), [is banned](#isbanned) or [is whitelisted](#iswhitelisted). These options actually require a proper OfflinePlayer instance to be present or will otherwise default to `false`.
This interface includes a [`OfflinePlayer` instance](#getplayer()) obtained from the server the plugin runs on alongside some getters to get if the player [has played on the server before](#hasplayedbefore()), [is banned](#isbanned()) or [is whitelisted](#iswhitelisted()). These options actually require a proper OfflinePlayer instance to be present or will otherwise default to `false`.
This class is useful for cases where you want to use the OfflinePlayer. Simply cast the GenericPlayer instance to a SpigotPlayer (Granted that it actually is an instance of it to begin with).
@@ -1,9 +1,9 @@
---
api: true
template: api-doc.html
methods:
- name: 'getWorlds'
description: 'Returns a `Map<String, World>` where the key is the name of the World and the value the world of the server.'
description: 'Returns a <code>Map&lt;String, World&gt;</code> where the key is the name of the World and the value the world of the server.'
returns: 'Possibly-empty Map containing pairs of Strings and Worlds.'
type:
name: 'Map<String, World>'
@@ -11,19 +11,16 @@ methods:
inherits:
'ch.andre601.advancedserverlist.api.objects.GenericServer':
link: '../../../api/ch.andre601.advancedserverlist.api/objects/genericserver.md'
link: '../../../../api/ch.andre601.advancedserverlist.api/objects/genericserver/'
list:
- name: 'getPlayersOnline()'
link: 'getplayersonline'
- name: 'getPlayersMax()'
link: 'getplayersmax'
- name: 'getHost()'
link: 'gethost'
- 'getPlayersOnline()'
- 'getPlayersMax()'
- 'getHost()'
---
# <api__interface></api__interface> BukkitServer
Spigot/Paper specific instance of a [`GenericServer`](../../../api/ch.andre601.advancedserverlist.api/objects/genericserver.md).
This interface includes a [Map of Worlds](#getworlds) the server currently has.
This interface includes a [Map of Worlds](#getworlds()) the server currently has.
This interface is useful for cases where you want to get the worlds of the Server itself, by simply casting your GenericServer instance to a BukkitServer one. Make sure to do proper instanceof checks first before attempting to cast.
Loaded 100 of 187 files, more files were not shown because too many files have changed in this diff. Show more