Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
4efa64d1d8
11 files changed
+862
-682
No files matched your search
+4
-4
@@ -1,5 +1,5 @@
|
||||
.idea
|
||||
*.iml
|
||||
.idea/
|
||||
.gradle/
|
||||
build/
|
||||
|
||||
.gradle
|
||||
build
|
||||
*.iml
|
||||
+13
-27
@@ -1,37 +1,23 @@
|
||||
plugins {
|
||||
`java-library`
|
||||
java
|
||||
id("com.github.johnrengelman.shadow") version ("7.0.0")
|
||||
}
|
||||
|
||||
group = "com.extendedclip.papi.expansion.server"
|
||||
version = "2.6.2"
|
||||
description = "PlaceholderAPI expansion for server placeholders"
|
||||
group = "at.helpch.placeholderapi.expansion"
|
||||
version = "3.0.0"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
||||
maven {
|
||||
name = "paper-repo"
|
||||
url = uri("https://repo.papermc.io/repository/maven-public/")
|
||||
}
|
||||
|
||||
maven {
|
||||
name = "placeholderapi"
|
||||
url = uri("https://repo.extendedclip.com/content/repositories/placeholderapi/")
|
||||
}
|
||||
maven("https://papermc.io/repo/repository/maven-public/")
|
||||
maven("https://repo.extendedclip.com/content/repositories/placeholderapi/")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("com.destroystokyo.paper:paper-api:1.16.5-R0.1-SNAPSHOT")
|
||||
compileOnly("me.clip:placeholderapi:2.11.5") {
|
||||
exclude(group = "org.bstats")
|
||||
compileOnly("io.papermc.paper:paper-api:1.17.1-R0.1-SNAPSHOT")
|
||||
compileOnly("me.clip:placeholderapi:2.11.5")
|
||||
}
|
||||
|
||||
tasks {
|
||||
shadowJar {
|
||||
archiveFileName.set("PAPI-Expansion-Server-${project.version}.jar")
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType<JavaCompile> {
|
||||
options.encoding = "UTF-8"
|
||||
}
|
||||
|
||||
configure<JavaPluginExtension> {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
rootProject.name = 'Server-Expansion'
|
||||
@@ -0,0 +1,255 @@
|
||||
package at.helpch.placeholderapi.expansion.server;
|
||||
|
||||
import at.helpch.placeholderapi.expansion.server.util.Logging;
|
||||
import at.helpch.placeholderapi.expansion.server.util.ServerUtil;
|
||||
import at.helpch.placeholderapi.expansion.server.util.TimeFormatter;
|
||||
import at.helpch.placeholderapi.expansion.server.util.TpsFormatter;
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import me.clip.placeholderapi.PlaceholderAPIPlugin;
|
||||
import me.clip.placeholderapi.expansion.Cacheable;
|
||||
import me.clip.placeholderapi.expansion.Configurable;
|
||||
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.World;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.time.ZoneId;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.ToIntFunction;
|
||||
|
||||
public final class ServerExpansion extends PlaceholderExpansion implements Cacheable, Configurable {
|
||||
|
||||
private final Cache<String, Integer> cache = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(1, TimeUnit.MINUTES)
|
||||
.build();
|
||||
|
||||
private final Runtime runtime = Runtime.getRuntime();
|
||||
|
||||
/**
|
||||
* Constant that represents how many bytes are in a mebibyte
|
||||
* <br/>
|
||||
* Required for the {@code ram_} placeholders, as the values provided by the system are in bytes
|
||||
*/
|
||||
private final int MiB = 1_048_576;
|
||||
|
||||
// ----- Values from config
|
||||
private String serverName;
|
||||
// -----
|
||||
|
||||
private TpsFormatter tpsFormatter;
|
||||
private TimeFormatter timeFormatter;
|
||||
|
||||
@Override
|
||||
public @NotNull String getIdentifier() {
|
||||
return "server";
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getAuthor() {
|
||||
return "HelpChat";
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getVersion() {
|
||||
return "3.0.0";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRegister() {
|
||||
this.serverName = getString("server_name", "A Minecraft Server");
|
||||
this.tpsFormatter = new TpsFormatter(
|
||||
getString("tps_color.low", "&c"),
|
||||
getString("tps_color.medium", "&e"),
|
||||
getString("tps_color.high", "&a")
|
||||
);
|
||||
this.timeFormatter = new TimeFormatter(
|
||||
Locale.forLanguageTag(this.getString("time.locale", Locale.getDefault().toLanguageTag())),
|
||||
ZoneId.of(this.getString("time.zone", ZoneId.systemDefault().getId())),
|
||||
Optional.ofNullable(getConfigSection("time.suffix")).map(section -> section.getValues(false)).orElseGet(HashMap::new)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
cache.invalidateAll();
|
||||
timeFormatter = null;
|
||||
tpsFormatter = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getDefaults() {
|
||||
return ImmutableMap.<String, Object>builder()
|
||||
.put("server_name", "A Minecraft Server")
|
||||
.put("time.locale", Locale.getDefault().toLanguageTag())
|
||||
.put("time.zone", ZoneId.systemDefault().getId())
|
||||
.put("time.suffix.week", "w")
|
||||
.put("time.suffix.day", "d")
|
||||
.put("time.suffix.hour", "h")
|
||||
.put("time.suffix.minute", "m")
|
||||
.put("time.suffix.second", "s")
|
||||
.put("tps_color.high", "&a")
|
||||
.put("tps_color.medium", "&e")
|
||||
.put("tps_color.low", "&c")
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get some data from all worlds
|
||||
*
|
||||
* @param function data to get (e.g. total entities)
|
||||
* @return sum
|
||||
*/
|
||||
private int getFromAllWorlds(@NotNull final ToIntFunction<? super World> function) {
|
||||
return Bukkit.getWorlds()
|
||||
.stream()
|
||||
.mapToInt(function)
|
||||
.sum();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a value from the {@link #cache}
|
||||
*
|
||||
* @param key key
|
||||
* @param callable cache method
|
||||
* @return value if found otherwise empty
|
||||
*/
|
||||
private @NotNull String getFromCache(@NotNull final String key, @NotNull final Callable<Integer> callable) {
|
||||
try {
|
||||
return String.valueOf(cache.get(key, callable));
|
||||
} catch (ExecutionException e) {
|
||||
Logging.error(e, "Could not get key \"{0}\" from cache", key);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a {@code boolean} as {@link PlaceholderAPIPlugin#booleanTrue()} or {@link PlaceholderAPIPlugin#booleanFalse()}
|
||||
*
|
||||
* @param bool boolean
|
||||
* @return {@link PlaceholderAPIPlugin#booleanTrue()} if the boolean is true, otherwise {@link PlaceholderAPIPlugin#booleanFalse()}
|
||||
*/
|
||||
private @NotNull String bool(final boolean bool) {
|
||||
return bool ? PlaceholderAPIPlugin.booleanTrue() : PlaceholderAPIPlugin.booleanFalse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of online players from a world
|
||||
*
|
||||
* @param worldName the name of the world
|
||||
* @return {@code -1} if {@code worldName} is empty or the world doesn't exist, otherwise {@link World#getPlayers()}.size()
|
||||
*/
|
||||
private int getOnlinePlayers(@NotNull final String worldName) {
|
||||
if (worldName.trim().isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return Optional.ofNullable(Bukkit.getWorld(worldName))
|
||||
.map(World::getPlayers)
|
||||
.map(List::size)
|
||||
.orElse(-1);
|
||||
}
|
||||
|
||||
@SuppressWarnings("SpellCheckingInspection")
|
||||
@Override
|
||||
public @Nullable String onRequest(OfflinePlayer player, @NotNull String params) {
|
||||
switch (params) {
|
||||
// ----- Players placeholders
|
||||
case "online":
|
||||
return String.valueOf(Bukkit.getOnlinePlayers().size());
|
||||
case "max_players":
|
||||
return String.valueOf(Bukkit.getMaxPlayers());
|
||||
case "unique_joins":
|
||||
return String.valueOf(Bukkit.getOfflinePlayers().length);
|
||||
// -----
|
||||
|
||||
// ----- Server software placeholders
|
||||
case "name":
|
||||
return this.serverName;
|
||||
case "variant":
|
||||
return ServerUtil.getVariant();
|
||||
case "version":
|
||||
return ServerUtil.getServerVersion();
|
||||
case "build":
|
||||
return ServerUtil.getBuild();
|
||||
case "version_build":
|
||||
case "version_full":
|
||||
return ServerUtil.getServerVersion() + '-' + ServerUtil.getBuild();
|
||||
// -----
|
||||
|
||||
// ----- RAM placeholders
|
||||
case "ram_used":
|
||||
return String.valueOf((runtime.totalMemory() - runtime.freeMemory()) / MiB);
|
||||
case "ram_free":
|
||||
return String.valueOf(runtime.freeMemory() / MiB);
|
||||
case "ram_total":
|
||||
return String.valueOf(runtime.totalMemory() / MiB);
|
||||
case "ram_max":
|
||||
return String.valueOf(runtime.maxMemory() / MiB);
|
||||
// -----
|
||||
|
||||
case "tps":
|
||||
return tpsFormatter.getTps(null);
|
||||
case "uptime":
|
||||
return timeFormatter.formatTimeInSeconds(TimeUnit.MILLISECONDS.toSeconds(ManagementFactory.getRuntimeMXBean().getUptime()));
|
||||
case "total_chunks":
|
||||
return getFromCache("chunks", () -> getFromAllWorlds(world -> world.getLoadedChunks().length));
|
||||
case "total_living_entities":
|
||||
return getFromCache("livingEntities", () -> getFromAllWorlds(world -> world.getLivingEntities().size()));
|
||||
case "total_entities":
|
||||
return getFromCache("totalEntities", () -> getFromAllWorlds(world -> world.getEntities().size()));
|
||||
case "has_whitelist":
|
||||
return bool(Bukkit.hasWhitelist());
|
||||
}
|
||||
|
||||
// tps_<type>
|
||||
if (params.startsWith("tps_")) {
|
||||
return tpsFormatter.getTps(params.substring("tps_".length()));
|
||||
}
|
||||
|
||||
// online_<world name>
|
||||
if (params.startsWith("online_")) {
|
||||
return String.valueOf(getOnlinePlayers(params.substring("online_".length())));
|
||||
}
|
||||
|
||||
// time_<simple date format>
|
||||
if (params.startsWith("time_")) {
|
||||
return timeFormatter.formatTime(params.substring("time_".length()));
|
||||
}
|
||||
|
||||
// countdown_raw_<date> or countdown_raw_<custom format>_<date>
|
||||
if (params.startsWith("countdown_raw_")) {
|
||||
return timeFormatter.calculateTimeBetweenWithoutFormat(player, params.substring("countdown_raw_".length()), true);
|
||||
}
|
||||
|
||||
// countdown_<date> or countdown_<custom format>_<date>
|
||||
if (params.startsWith("countdown_")) {
|
||||
return timeFormatter.calculateTimeBetween(player, params.substring("countdown_".length()), true, true);
|
||||
}
|
||||
|
||||
// countup_raw_<date> or countup_raw_<custom format>_<date>
|
||||
if (params.startsWith("countup_raw_")) {
|
||||
return timeFormatter.calculateTimeBetweenWithoutFormat(player, params.substring("countup_raw_".length()), false);
|
||||
}
|
||||
|
||||
// countup_<date> or countup_<custom format>_<date>
|
||||
if (params.startsWith("countup_")) {
|
||||
return timeFormatter.calculateTimeBetween(player, params.substring("countup_".length()), false, true);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package at.helpch.placeholderapi.expansion.server.util;
|
||||
|
||||
import me.clip.placeholderapi.PlaceholderAPIPlugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public final class Logging {
|
||||
|
||||
private static void log(@NotNull final Level level, @Nullable final Throwable throwable, @NotNull final String message, @NotNull final Object... args) {
|
||||
PlaceholderAPIPlugin.getInstance()
|
||||
.getLogger()
|
||||
.log(level, "[server] " + MessageFormat.format(message, args), throwable);
|
||||
}
|
||||
|
||||
public static void warn(@NotNull final String message, @NotNull final Object... args) {
|
||||
log(Level.WARNING, null, message, args);
|
||||
}
|
||||
|
||||
public static void error(@NotNull final Throwable throwable, @NotNull final String message, @NotNull final Object... args) {
|
||||
log(Level.SEVERE, throwable, message, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
package at.helpch.placeholderapi.expansion.server.util;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
public final class ServerUtil {
|
||||
|
||||
private static final Map<String, String> variants = new TreeMap<>();
|
||||
private static final String variant;
|
||||
private static final String serverVersion;
|
||||
private static final String build;
|
||||
|
||||
// TPS stuff
|
||||
private static final Object craftServer;
|
||||
private static final Field tpsField;
|
||||
private static boolean hasTpsMethod; // Paper and its forks have Bukkit#getTps
|
||||
|
||||
static {
|
||||
variants.put("net.pl3x.purpur.PurpurConfig", "Purpur");
|
||||
variants.put("gg.airplane.AirplaneConfig", "Airplane");
|
||||
variants.put("com.tuinity.tuinity.config.TuinityConfig", "Tuinity");
|
||||
variants.put("io.papermc.paper.configuration.ConfigurationLoaders", "Paper"); // New config location for Paper 1.19+
|
||||
variants.put("com.destroystokyo.paper.PaperConfig", "Paper"); // Still supported by Paper, but deprecated.
|
||||
|
||||
craftServer = getCraftServer();
|
||||
tpsField = getTpsHandler();
|
||||
variant = findVariant();
|
||||
serverVersion = Bukkit.getBukkitVersion().split("-")[0]; // The version is formatted as 1.20.1-R0.1-SNAPSHOT
|
||||
build = findBuild();
|
||||
}
|
||||
|
||||
private static String findVariant() {
|
||||
for (final Map.Entry<String, String> entry : variants.entrySet()) {
|
||||
try {
|
||||
Class.forName(entry.getKey());
|
||||
return entry.getValue();
|
||||
} catch (ClassNotFoundException ignored) { }
|
||||
}
|
||||
|
||||
// Source: https://github.com/PaperMC/Paper/blob/f7717c3/patches/server/0027-Show-Paper-in-client-crashes-server-lists-and-Mojang.patch#L17
|
||||
if (craftServer != null) {
|
||||
try {
|
||||
return (String) craftServer.getClass()
|
||||
.getMethod("getServerModName")
|
||||
.invoke(craftServer);
|
||||
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException ignored) { }
|
||||
}
|
||||
|
||||
try {
|
||||
Class.forName("org.spigotmc.SpigotConfig");
|
||||
return "Spigot";
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
return "Unknown"; // Not even spigot??
|
||||
}
|
||||
}
|
||||
|
||||
private static Object getCraftServer() {
|
||||
try {
|
||||
Class<?> clas;
|
||||
|
||||
if (VersionHelper.IS_1_17_OR_HIGHER) {
|
||||
clas = Class.forName("net.minecraft.server.MinecraftServer");
|
||||
} else {
|
||||
clas = Class.forName(String.format("net.minecraft.server.%s.MinecraftServer", VersionHelper.NMS_VERSION));
|
||||
}
|
||||
|
||||
return clas.getMethod("getServer").invoke(null);
|
||||
} catch (ClassNotFoundException e) {
|
||||
Logging.error(e, "Could not find class MinecraftServer");
|
||||
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
|
||||
Logging.error(e, "Could not invoke method MinecraftServer#getServer");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Field getTpsHandler() {
|
||||
try {
|
||||
Bukkit.class.getMethod("getTPS");
|
||||
hasTpsMethod = true;
|
||||
return null;
|
||||
} catch (NoSuchMethodException ignored) { }
|
||||
|
||||
if (craftServer == null) {
|
||||
Logging.warn("Could not get field 'recentTps' from class MinecraftServer because variable 'craftServer' is null");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return craftServer.getClass().getField("recentTps");
|
||||
} catch (NoSuchFieldException e) {
|
||||
Logging.error(e, "Could not find field 'recentTps' in class {0}", craftServer.getClass().getName());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String findBuild() {
|
||||
String[] buildParts = Bukkit.getVersion().split("-");
|
||||
|
||||
switch (getVariant().toLowerCase()) {
|
||||
case "spigot":
|
||||
return buildParts[0];
|
||||
|
||||
// Source: https://github.com/PaperMC/Paper/blob/0ae58c0/patches/server/0001-Setup-Gradle-project.patch#L75
|
||||
case "paper":
|
||||
// Source: https://github.com/Tuinity/Tuinity/blob/4c31ea1/patches/server/0001-Build-changes.patch#L34
|
||||
case "tuinity":
|
||||
// Source: https://github.com/PurpurMC/Purpur/blob/c049bfb/patches/server/0002-Rebrand.patch#L44
|
||||
case "purpur":
|
||||
// Source: https://github.com/TECHNOVE/Airplane/blob/af3563c/patches/server/0002-Airplane-Branding-Changes.patch#L41
|
||||
case "airplane": {
|
||||
// Format: git-<Paper/Tuinity/Purpur/Airplane>-<version>
|
||||
if (buildParts.length >= 3) {
|
||||
if (buildParts[2].contains(" ")) {
|
||||
return buildParts[2].substring(0, buildParts[2].indexOf(" "));
|
||||
} else {
|
||||
return buildParts[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "Unknonw";
|
||||
}
|
||||
|
||||
public static String getVariant() {
|
||||
return variant;
|
||||
}
|
||||
|
||||
public static String getServerVersion() {
|
||||
return serverVersion;
|
||||
}
|
||||
|
||||
public static String getBuild() {
|
||||
return build;
|
||||
}
|
||||
|
||||
public static double[] getTps() {
|
||||
if (hasTpsMethod) {
|
||||
return Bukkit.getTPS();
|
||||
}
|
||||
|
||||
if (craftServer == null || tpsField == null) {
|
||||
return new double[]{0, 0, 0};
|
||||
}
|
||||
|
||||
try {
|
||||
return (double[]) tpsField.get(craftServer);
|
||||
} catch (IllegalAccessException e) {
|
||||
return new double[]{0, 0, 0};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
package at.helpch.placeholderapi.expansion.server.util;
|
||||
|
||||
import me.clip.placeholderapi.PlaceholderAPI;
|
||||
import me.clip.placeholderapi.PlaceholderAPIPlugin;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
public final class TimeFormatter {
|
||||
|
||||
private final Map<String, DateTimeFormatter> dateFormatsCache = new HashMap<>();
|
||||
|
||||
private final Locale timeLocale;
|
||||
private final ZoneId timeZone;
|
||||
private final Map<@NotNull String, @Nullable Object> timeUnitSuffixes;
|
||||
|
||||
public TimeFormatter(
|
||||
@NotNull final Locale timeLocale, @NotNull final ZoneId timeZone,
|
||||
@NotNull Map<@NotNull String, @Nullable Object> timeUnitSuffixes
|
||||
) {
|
||||
this.timeLocale = timeLocale;
|
||||
this.timeZone = timeZone;
|
||||
this.timeUnitSuffixes = timeUnitSuffixes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a string to a {@link DateTimeFormatter} and cache it
|
||||
*
|
||||
* @param pattern pattern to parse
|
||||
* @return {@link DateTimeFormatter} if the argument is a valid pattern, otherwise null
|
||||
*/
|
||||
public @Nullable DateTimeFormatter parseFormat(@NotNull final String pattern) {
|
||||
return dateFormatsCache.computeIfAbsent(pattern, __ -> {
|
||||
try {
|
||||
return DateTimeFormatter.ofPattern(pattern, timeLocale);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Logging.warn("Could not parse DateTimeFormatter from string \"{0}\"", pattern);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the current time with the given format
|
||||
*
|
||||
* @param format format
|
||||
* @return {@code null} if the format is empty or invalid, otherwise {@link LocalDateTime#now(ZoneId)} formatted
|
||||
*/
|
||||
public @Nullable String formatTime(@NotNull final String format) {
|
||||
if (format.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Optional.ofNullable(parseFormat(format))
|
||||
.map(formatter -> LocalDateTime.now(timeZone).format(formatter))
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format time as {@code #w #d #h #m #s}
|
||||
*
|
||||
* @param time time in seconds
|
||||
* @return time formatted
|
||||
*/
|
||||
public @NotNull String formatTimeInSeconds(long time) {
|
||||
final StringJoiner joiner = new StringJoiner(" ");
|
||||
final BiConsumer<@NotNull Long, @Nullable Object> appendTime = (value, unit) -> {
|
||||
if (value > 0 && unit != null) {
|
||||
joiner.add(value + unit.toString());
|
||||
}
|
||||
};
|
||||
|
||||
long seconds = time;
|
||||
long minutes = seconds / 60;
|
||||
long hours = minutes / 60;
|
||||
long days = hours / 24;
|
||||
final long weeks = days / 7;
|
||||
|
||||
seconds %= 60;
|
||||
minutes %= 60;
|
||||
hours %= 24;
|
||||
days %= 7;
|
||||
|
||||
appendTime.accept(weeks, timeUnitSuffixes.get("week"));
|
||||
appendTime.accept(days, timeUnitSuffixes.get("day"));
|
||||
appendTime.accept(hours, timeUnitSuffixes.get("hour"));
|
||||
appendTime.accept(minutes, timeUnitSuffixes.get("minute"));
|
||||
appendTime.accept(seconds, timeUnitSuffixes.get("second"));
|
||||
return joiner.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the time between {@link LocalDateTime#now(ZoneId)} and another date and return a formatted value
|
||||
* using {@link #formatTimeInSeconds(long)} if {@code formatTime} is {@code true}.
|
||||
*
|
||||
* @param player player
|
||||
* @param args arguments received on {@link me.clip.placeholderapi.expansion.PlaceholderExpansion#onRequest(OfflinePlayer, String)}
|
||||
* @param isCountdown whether the time calculated is for a countdown or a count-up
|
||||
* @param formatTime whether the time should be formatted with {@link #formatTimeInSeconds(long)}
|
||||
* @return time formatter
|
||||
*/
|
||||
public @NotNull String calculateTimeBetween(
|
||||
@Nullable final OfflinePlayer player, @NotNull final String args,
|
||||
final boolean isCountdown, final boolean formatTime
|
||||
) {
|
||||
DateTimeFormatter formatter;
|
||||
String otherDateString;
|
||||
|
||||
// Placeholder with custom time format
|
||||
if (args.contains("_")) {
|
||||
final String[] parts = PlaceholderAPI.setBracketPlaceholders(player, args).split("_");
|
||||
|
||||
if (parts.length != 2) {
|
||||
return "invalid format and time";
|
||||
}
|
||||
|
||||
formatter = this.parseFormat(parts[0]);
|
||||
otherDateString = parts[1];
|
||||
} else {
|
||||
formatter = this.parseFormat(PlaceholderAPIPlugin.getInstance().getPlaceholderAPIConfig().dateFormat());
|
||||
otherDateString = args;
|
||||
}
|
||||
|
||||
if (formatter == null) {
|
||||
return "invalid date format";
|
||||
}
|
||||
|
||||
try {
|
||||
LocalDateTime now = LocalDateTime.now(timeZone);
|
||||
LocalDateTime otherDate = LocalDateTime.parse(otherDateString, formatter);
|
||||
|
||||
if (otherDate.isEqual(now)) {
|
||||
return "0";
|
||||
}
|
||||
|
||||
// 'otherDate' should be after 'now' for countdowns and before for count-ups
|
||||
if ((isCountdown && otherDate.isBefore(now)) || (!isCountdown && otherDate.isAfter(now))) {
|
||||
return "0";
|
||||
}
|
||||
|
||||
final long time = isCountdown ? now.until(otherDate, ChronoUnit.SECONDS) : otherDate.until(now, ChronoUnit.SECONDS);
|
||||
return formatTime ? this.formatTimeInSeconds(time) : String.valueOf(time);
|
||||
} catch (DateTimeParseException e) {
|
||||
final String type = isCountdown ? "countdown" : "count-up";
|
||||
Logging.error(e, "Could not calculate {0} (format: \"{1}\", other date: \"{2}\")", type, formatter.toString(), otherDateString);
|
||||
return "invalid date";
|
||||
}
|
||||
}
|
||||
|
||||
public @NotNull String calculateTimeBetweenWithoutFormat(
|
||||
@Nullable final OfflinePlayer player, @NotNull final String args,
|
||||
final boolean isCountdown
|
||||
) {
|
||||
return calculateTimeBetween(player, args, isCountdown, false);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package at.helpch.placeholderapi.expansion.server.util;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.StringJoiner;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class TpsFormatter {
|
||||
|
||||
private final String lowColor;
|
||||
private final String mediumColor;
|
||||
private final String highColor;
|
||||
|
||||
/**
|
||||
* @param lowColor color for {@code tps < 16.0}
|
||||
* @param mediumColor color for {@code tps < 18.0}
|
||||
* @param highColor color for {@code tps > 18.0}
|
||||
*/
|
||||
public TpsFormatter(@NotNull final String lowColor, @NotNull final String mediumColor, @NotNull final String highColor) {
|
||||
this.lowColor = ChatColor.translateAlternateColorCodes('&', lowColor);
|
||||
this.mediumColor = ChatColor.translateAlternateColorCodes('&', mediumColor);
|
||||
this.highColor = ChatColor.translateAlternateColorCodes('&', highColor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Round the tps and append a {@code *} before if the value is higher than 20.0
|
||||
*
|
||||
* @param tps tps
|
||||
* @return tps rounded
|
||||
*/
|
||||
private @NotNull String round(final double tps) {
|
||||
final double finalTps = Math.min(Math.round(tps), 20.0);
|
||||
return (tps > 20.0 ? "*" : "") + finalTps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the color based on the tps value
|
||||
*
|
||||
* @param tps tps
|
||||
* @return color for the tps
|
||||
*/
|
||||
private @NotNull String getColor(final double tps) {
|
||||
return (tps > 18.0) ? highColor : (tps > 16.0) ? mediumColor : lowColor;
|
||||
}
|
||||
|
||||
private @NotNull String getPercent(final double tps) {
|
||||
final double percent = Math.min(Math.round(100 / 20.0 * tps), 100.0);
|
||||
return (tps > 20.0 ? "*" : "") + percent + '%';
|
||||
}
|
||||
|
||||
private @NotNull String getColoredTps(final double tps) {
|
||||
return getColor(tps) + round(tps);
|
||||
}
|
||||
|
||||
private @NotNull String getColoredTpsPercent(final double tps) {
|
||||
return getColor(tps) + getPercent(tps);
|
||||
}
|
||||
|
||||
private @NotNull String getAllTps(@NotNull final Function<Double, String> formatter) {
|
||||
final StringJoiner joiner = new StringJoiner(ChatColor.GRAY + ", ");
|
||||
|
||||
for (final double tps : ServerUtil.getTps()) {
|
||||
joiner.add(formatter.apply(tps));
|
||||
}
|
||||
|
||||
return joiner.toString();
|
||||
}
|
||||
|
||||
public @Nullable String getTps(@Nullable final String type) {
|
||||
if (type == null || type.isEmpty()) {
|
||||
return getAllTps(this::getColoredTps);
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case "1":
|
||||
case "one":
|
||||
return round(ServerUtil.getTps()[0]);
|
||||
|
||||
case "5":
|
||||
case "five":
|
||||
return round(ServerUtil.getTps()[1]);
|
||||
|
||||
case "15":
|
||||
case "fifteen":
|
||||
return round(ServerUtil.getTps()[2]);
|
||||
|
||||
case "1_colored":
|
||||
case "one_colored":
|
||||
return getColoredTps(ServerUtil.getTps()[0]);
|
||||
|
||||
case "5_colored":
|
||||
case "five_colored":
|
||||
return getColoredTps(ServerUtil.getTps()[1]);
|
||||
|
||||
case "15_colored":
|
||||
case "fifteen_colored":
|
||||
return getColoredTps(ServerUtil.getTps()[2]);
|
||||
|
||||
case "percent":
|
||||
return getAllTps(this::getColoredTpsPercent);
|
||||
|
||||
case "1_percent":
|
||||
case "one_percent":
|
||||
return getPercent(ServerUtil.getTps()[0]);
|
||||
|
||||
case "5_percent":
|
||||
case "five_percent":
|
||||
return getPercent(ServerUtil.getTps()[1]);
|
||||
|
||||
case "15_percent":
|
||||
case "fifteen_percent":
|
||||
return getPercent(ServerUtil.getTps()[2]);
|
||||
|
||||
case "1_percent_colored":
|
||||
case "one_percent_colored":
|
||||
return getColoredTpsPercent(ServerUtil.getTps()[0]);
|
||||
|
||||
case "5_percent_colored":
|
||||
case "five_percent_colored":
|
||||
return getColoredTpsPercent(ServerUtil.getTps()[1]);
|
||||
|
||||
case "15_percent_colored":
|
||||
case "fifteen_percent_colored":
|
||||
return getColoredTpsPercent(ServerUtil.getTps()[2]);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* MIT License
|
||||
* <p>
|
||||
* Copyright (c) 2021 TriumphTeam
|
||||
* <p>
|
||||
* 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:
|
||||
* <p>
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
* <p>
|
||||
* 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 at.helpch.placeholderapi.expansion.server.util;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Class for detecting server version for legacy support :(
|
||||
*/
|
||||
public final class VersionHelper {
|
||||
|
||||
public static final String NMS_VERSION = getNmsVersion();
|
||||
|
||||
private static final int V1_17 = 1_17_0;
|
||||
|
||||
private static final int CURRENT_VERSION = getCurrentVersion();
|
||||
|
||||
private static final boolean IS_PAPER = checkPaper();
|
||||
|
||||
public static final boolean IS_1_17_OR_HIGHER = CURRENT_VERSION >= V1_17;
|
||||
|
||||
/**
|
||||
* Check if the server has access to the Paper API
|
||||
* Taken from <a href="https://github.com/PaperMC/PaperLib">PaperLib</a>
|
||||
*
|
||||
* @return True if on Paper server (or forks), false anything else
|
||||
*/
|
||||
private static boolean checkPaper() {
|
||||
final String[] configClasses = {"io.papermc.paper.configuration.ConfigurationLoaders", "com.destroystokyo.paper.PaperConfig"};
|
||||
|
||||
for (String configClass : configClasses) {
|
||||
try {
|
||||
Class.forName(configClass);
|
||||
return true;
|
||||
} catch (ClassNotFoundException ignored) { }
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current server version
|
||||
*
|
||||
* @return A protocol like number representing the version, for example 1.16.5 - 1165
|
||||
*/
|
||||
private static int getCurrentVersion() {
|
||||
// No need to cache since will only run once
|
||||
final Matcher matcher = Pattern.compile("(?<version>\\d+\\.\\d+)(?<patch>\\.\\d+)?").matcher(Bukkit.getBukkitVersion());
|
||||
|
||||
final StringBuilder stringBuilder = new StringBuilder();
|
||||
if (matcher.find()) {
|
||||
stringBuilder.append(matcher.group("version").replace(".", ""));
|
||||
final String patch = matcher.group("patch");
|
||||
if (patch == null) stringBuilder.append("0");
|
||||
else stringBuilder.append(patch.replace(".", ""));
|
||||
}
|
||||
|
||||
//noinspection UnstableApiUsage
|
||||
final Integer version = Ints.tryParse(stringBuilder.toString());
|
||||
|
||||
// Should never fail
|
||||
if (version == null) throw new IllegalArgumentException("Could not retrieve server version!");
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
private static String getNmsVersion() {
|
||||
final String version = Bukkit.getServer().getClass().getPackage().getName();
|
||||
return version.substring(version.lastIndexOf('.') + 1);
|
||||
}
|
||||
|
||||
public static Class<?> craftClass(@NotNull final String name) throws ClassNotFoundException {
|
||||
return Class.forName("org.bukkit.craftbukkit." + NMS_VERSION + "." + name);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,506 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* Server-Expansion
|
||||
* Copyright (C) 2018 Ryan McCarthy
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
*/
|
||||
package com.extendedclip.papi.expansion.server;
|
||||
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import me.clip.placeholderapi.PlaceholderAPI;
|
||||
import me.clip.placeholderapi.PlaceholderAPIPlugin;
|
||||
import me.clip.placeholderapi.expansion.Cacheable;
|
||||
import me.clip.placeholderapi.expansion.Configurable;
|
||||
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.Duration;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class ServerExpansion extends PlaceholderExpansion implements Cacheable, Configurable {
|
||||
|
||||
private ServerUtils serverUtils = null;
|
||||
|
||||
private final Map<String, SimpleDateFormat> dateFormats = new HashMap<>();
|
||||
private final Runtime runtime = Runtime.getRuntime();
|
||||
|
||||
// config stuff
|
||||
private String serverName;
|
||||
private String low = "&c";
|
||||
private String medium = "&e";
|
||||
private String high = "&a";
|
||||
// -----
|
||||
|
||||
private final Cache<String, Integer> cache = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(1, TimeUnit.MINUTES)
|
||||
.build();
|
||||
|
||||
@Override
|
||||
public boolean canRegister() {
|
||||
serverName = this.getString("server_name", "A Minecraft Server");
|
||||
low = this.getString("tps_color.low", "&c");
|
||||
medium = this.getString("tps_color.medium", "&e");
|
||||
high = this.getString("tps_color.high", "&a");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
dateFormats.clear();
|
||||
serverUtils = null;
|
||||
cache.invalidateAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getIdentifier() {
|
||||
return "server";
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getAuthor() {
|
||||
return "clip";
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getVersion() {
|
||||
return "2.6.2";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getDefaults() {
|
||||
final Map<String, Object> defaults = new HashMap<>();
|
||||
defaults.put("tps_color.high", "&a");
|
||||
defaults.put("tps_color.medium", "&e");
|
||||
defaults.put("tps_color.low", "&c");
|
||||
defaults.put("server_name", "A Minecraft Server");
|
||||
return defaults;
|
||||
}
|
||||
|
||||
private @Nullable String getCached(String key, Callable<Integer> callable) {
|
||||
try {
|
||||
return String.valueOf(cache.get(key, callable));
|
||||
} catch (ExecutionException e) {
|
||||
if (getPlaceholderAPI().getPlaceholderAPIConfig().isDebugMode()) {
|
||||
getPlaceholderAPI().getLogger().log(Level.SEVERE, "[server] Could not access cache key " + key, e);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private int getChunks(){
|
||||
return Bukkit.getWorlds()
|
||||
.stream()
|
||||
.mapToInt(world -> world.getLoadedChunks().length)
|
||||
.sum();
|
||||
}
|
||||
|
||||
private int getLivingEntities(){
|
||||
return Bukkit.getWorlds()
|
||||
.stream()
|
||||
.mapToInt(world -> world.getLivingEntities().size())
|
||||
.sum();
|
||||
}
|
||||
|
||||
private Integer getTotalEntities(){
|
||||
return Bukkit.getWorlds()
|
||||
.stream()
|
||||
.mapToInt(world -> world.getEntities().size())
|
||||
.sum();
|
||||
}
|
||||
|
||||
// Per world counts
|
||||
|
||||
private int getLocalChunks(String world){
|
||||
return Bukkit.getWorld(world).getLoadedChunks().length;
|
||||
}
|
||||
|
||||
private int getLocalPlayers(String world){
|
||||
return Bukkit.getWorld(world).getPlayers().size();
|
||||
}
|
||||
|
||||
private int getLocalEntities(String world){
|
||||
return Bukkit.getWorld(world).getEntities().size();
|
||||
}
|
||||
|
||||
private int getLocalLivingEntities(String world){
|
||||
return Bukkit.getWorld(world).getLivingEntities().size();
|
||||
}
|
||||
|
||||
// Paper method
|
||||
private int getLocalTileEntities(String world){
|
||||
return Bukkit.getWorld(world).getTileEntityCount();
|
||||
}
|
||||
|
||||
private int getLocalTickableTileEntities(String world){
|
||||
return Bukkit.getWorld(world).getTickableTileEntityCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String onRequest(OfflinePlayer p, @NotNull String identifier) {
|
||||
final int MB = 1048576;
|
||||
if (serverUtils == null) {
|
||||
serverUtils = new ServerUtils();
|
||||
}
|
||||
|
||||
switch (identifier) {
|
||||
// Players placeholders
|
||||
case "online":
|
||||
return String.valueOf(Bukkit.getOnlinePlayers().size());
|
||||
case "max_players":
|
||||
return String.valueOf(Bukkit.getMaxPlayers());
|
||||
case "unique_joins":
|
||||
return String.valueOf(Bukkit.getOfflinePlayers().length);
|
||||
// -----
|
||||
|
||||
// Version placeholders
|
||||
case "version":
|
||||
return serverUtils.getVersion();
|
||||
case "build":
|
||||
return serverUtils.getBuild();
|
||||
case "version_build":
|
||||
case "version_full":
|
||||
return serverUtils.getVersion() + '-' + serverUtils.getBuild();
|
||||
// -----
|
||||
|
||||
// Ram placeholders
|
||||
case "ram_used":
|
||||
return String.valueOf((runtime.totalMemory() - runtime.freeMemory()) / MB);
|
||||
case "ram_free":
|
||||
return String.valueOf(runtime.freeMemory() / MB);
|
||||
case "ram_total":
|
||||
return String.valueOf(runtime.totalMemory() / MB);
|
||||
case "ram_max":
|
||||
return String.valueOf(runtime.maxMemory() / MB);
|
||||
// -----
|
||||
|
||||
// Identity placeholders
|
||||
case "name":
|
||||
return serverName == null ? "" : serverName;
|
||||
case "variant":
|
||||
return serverUtils.getServerVariant();
|
||||
// -----
|
||||
|
||||
// Other placeholders
|
||||
case "tps":
|
||||
return getTps(null);
|
||||
case "uptime":
|
||||
long seconds = TimeUnit.MILLISECONDS.toSeconds(ManagementFactory.getRuntimeMXBean().getUptime());
|
||||
return formatTime(Duration.of(seconds, ChronoUnit.SECONDS));
|
||||
case "total_chunks":
|
||||
return getCached("chunks", this::getChunks);
|
||||
case "total_living_entities":
|
||||
return getCached("livingEntities", this::getLivingEntities);
|
||||
case "total_entities":
|
||||
return getCached("totalEntities", this::getTotalEntities);
|
||||
case "has_whitelist":
|
||||
return Bukkit.getServer().hasWhitelist() ? PlaceholderAPIPlugin.booleanTrue() : PlaceholderAPIPlugin.booleanFalse();
|
||||
}
|
||||
|
||||
if (identifier.startsWith("tps_")) {
|
||||
identifier = identifier.replace("tps_", "");
|
||||
return getTps(identifier);
|
||||
}
|
||||
|
||||
if (identifier.startsWith("online_")) {
|
||||
|
||||
identifier = identifier.replace("online_", "");
|
||||
|
||||
int i = 0;
|
||||
|
||||
for (Player o : Bukkit.getOnlinePlayers()) {
|
||||
if (o.getWorld().getName().equals(identifier)) {
|
||||
i = i + 1;
|
||||
}
|
||||
}
|
||||
return String.valueOf(i);
|
||||
}
|
||||
|
||||
if (identifier.startsWith("countdown_")) {
|
||||
String time = identifier.replace("countdown_", "");
|
||||
|
||||
if (!time.contains("_")) {
|
||||
|
||||
Date then;
|
||||
|
||||
try {
|
||||
then = PlaceholderAPIPlugin.getDateFormat().parse(time);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Date now = new Date();
|
||||
|
||||
long between = then.getTime() - now.getTime();
|
||||
|
||||
if (between <= 0) {
|
||||
return "0";
|
||||
}
|
||||
|
||||
return formatTime(Duration.of((int) TimeUnit.MILLISECONDS.toSeconds(between), ChronoUnit.SECONDS));
|
||||
|
||||
} else {
|
||||
|
||||
String[] parts = PlaceholderAPI.setBracketPlaceholders(p, time).split("_");
|
||||
|
||||
if (parts.length != 2) {
|
||||
return "invalid format and time";
|
||||
}
|
||||
|
||||
time = parts[1];
|
||||
|
||||
String format = parts[0];
|
||||
|
||||
SimpleDateFormat f;
|
||||
|
||||
try {
|
||||
f = new SimpleDateFormat(format);
|
||||
} catch (Exception e) {
|
||||
return "invalid date format";
|
||||
}
|
||||
|
||||
Date then;
|
||||
|
||||
try {
|
||||
then = f.parse(time);
|
||||
} catch (Exception e) {
|
||||
return "invalid date";
|
||||
}
|
||||
|
||||
long t = System.currentTimeMillis();
|
||||
|
||||
long between = then.getTime() - t;
|
||||
|
||||
if (between <= 0) {
|
||||
return "0";
|
||||
}
|
||||
|
||||
return formatTime(Duration.of((int) TimeUnit.MILLISECONDS.toSeconds(between), ChronoUnit.SECONDS));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (identifier.startsWith("time_")) {
|
||||
|
||||
identifier = identifier.replace("time_", "");
|
||||
|
||||
if (dateFormats.containsKey(identifier)) {
|
||||
return dateFormats.get(identifier).format(new Date());
|
||||
}
|
||||
|
||||
try {
|
||||
SimpleDateFormat format = new SimpleDateFormat(identifier);
|
||||
|
||||
dateFormats.put(identifier, format);
|
||||
|
||||
return format.format(new Date());
|
||||
} catch (NullPointerException | IllegalArgumentException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Per world counts placeholders
|
||||
|
||||
if (identifier.startsWith("total_chunks_")) {
|
||||
identifier = identifier.replace("total_chunks_", "");
|
||||
return String.valueOf(getLocalChunks(identifier));
|
||||
}
|
||||
|
||||
if (identifier.startsWith("total_players_")) {
|
||||
identifier = identifier.replace("total_players_", "");
|
||||
return String.valueOf(getLocalPlayers(identifier));
|
||||
}
|
||||
|
||||
if (identifier.startsWith("total_entities_")) {
|
||||
identifier = identifier.replace("total_entities_", "");
|
||||
return String.valueOf(getLocalEntities(identifier));
|
||||
}
|
||||
|
||||
if (identifier.startsWith("total_living_entities_")) {
|
||||
identifier = identifier.replace("total_living_entities_", "");
|
||||
return String.valueOf(getLocalLivingEntities(identifier));
|
||||
}
|
||||
|
||||
if (identifier.startsWith("total_tile_entities_")) {
|
||||
identifier = identifier.replace("total_tile_entities_", "");
|
||||
return String.valueOf(getLocalTileEntities(identifier));
|
||||
}
|
||||
|
||||
if (identifier.startsWith("total_tick_tile_entities_")) {
|
||||
identifier = identifier.replace("total_tick_tile_entities_", "");
|
||||
return String.valueOf(getLocalTickableTileEntities(identifier));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getTps(String arg) {
|
||||
if (serverUtils.getServerVariant().equals("Folia")) return null;
|
||||
|
||||
if (arg == null || arg.isEmpty()) {
|
||||
StringJoiner joiner = new StringJoiner(ChatColor.GRAY + ", ");
|
||||
for (double tps : serverUtils.getTps()) {
|
||||
joiner.add(getColoredTps(tps));
|
||||
}
|
||||
return joiner.toString();
|
||||
}
|
||||
switch (arg) {
|
||||
case "1":
|
||||
case "one":
|
||||
return fix(serverUtils.getTps()[0]);
|
||||
case "5":
|
||||
case "five":
|
||||
return fix(serverUtils.getTps()[1]);
|
||||
case "15":
|
||||
case "fifteen":
|
||||
return fix(serverUtils.getTps()[2]);
|
||||
case "1_colored":
|
||||
case "one_colored":
|
||||
return getColoredTps(serverUtils.getTps()[0]);
|
||||
case "5_colored":
|
||||
case "five_colored":
|
||||
return getColoredTps(serverUtils.getTps()[1]);
|
||||
case "15_colored":
|
||||
case "fifteen_colored":
|
||||
return getColoredTps(serverUtils.getTps()[2]);
|
||||
case "percent": {
|
||||
final StringJoiner joiner = new StringJoiner(ChatColor.GRAY + ", ");
|
||||
|
||||
for (double t : serverUtils.getTps()) {
|
||||
joiner.add(getColoredTpsPercent(t));
|
||||
}
|
||||
|
||||
return joiner.toString();
|
||||
}
|
||||
case "1_percent":
|
||||
case "one_percent":
|
||||
return getPercent(serverUtils.getTps()[0]);
|
||||
case "5_percent":
|
||||
case "five_percent":
|
||||
return getPercent(serverUtils.getTps()[1]);
|
||||
case "15_percent":
|
||||
case "fifteen_percent":
|
||||
return getPercent(serverUtils.getTps()[2]);
|
||||
case "1_percent_colored":
|
||||
case "one_percent_colored":
|
||||
return getColoredTpsPercent(serverUtils.getTps()[0]);
|
||||
case "5_percent_colored":
|
||||
case "five_percent_colored":
|
||||
return getColoredTpsPercent(serverUtils.getTps()[1]);
|
||||
case "15_percent_colored":
|
||||
case "fifteen_percent_colored":
|
||||
return getColoredTpsPercent(serverUtils.getTps()[2]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Sxtanna
|
||||
*/
|
||||
public static String formatTime(final Duration duration) {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
|
||||
long seconds = duration.getSeconds();
|
||||
long minutes = seconds / 60;
|
||||
long hours = minutes / 60;
|
||||
long days = hours / 24;
|
||||
final long weeks = days / 7;
|
||||
|
||||
seconds %= 60;
|
||||
minutes %= 60;
|
||||
hours %= 24;
|
||||
days %= 7;
|
||||
|
||||
if (seconds > 0) {
|
||||
builder.insert(0, seconds + "s");
|
||||
}
|
||||
|
||||
if (minutes > 0) {
|
||||
if (builder.length() > 0) {
|
||||
builder.insert(0, ' ');
|
||||
}
|
||||
|
||||
builder.insert(0, minutes + "m");
|
||||
}
|
||||
|
||||
if (hours > 0) {
|
||||
if (builder.length() > 0) {
|
||||
builder.insert(0, ' ');
|
||||
}
|
||||
|
||||
builder.insert(0, hours + "h");
|
||||
}
|
||||
|
||||
if (days > 0) {
|
||||
if (builder.length() > 0) {
|
||||
builder.insert(0, ' ');
|
||||
}
|
||||
|
||||
builder.insert(0, days + "d");
|
||||
}
|
||||
|
||||
if (weeks > 0) {
|
||||
if (builder.length() > 0) {
|
||||
builder.insert(0, ' ');
|
||||
}
|
||||
|
||||
builder.insert(0, weeks + "w");
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private String fix(double tps) {
|
||||
String finalTps = String.format("%.2f", tps);
|
||||
|
||||
return (tps > 20.0 ? "*" : "") + finalTps;
|
||||
}
|
||||
|
||||
private String color(double tps) {
|
||||
return ChatColor.translateAlternateColorCodes('&', (tps > 18.0) ? high : (tps > 16.0) ? medium : low);
|
||||
}
|
||||
|
||||
private String getColoredTps(double tps) {
|
||||
return color(tps) + fix(tps);
|
||||
}
|
||||
|
||||
private String getColoredTpsPercent(double tps){
|
||||
return color(tps) + getPercent(tps);
|
||||
}
|
||||
|
||||
private String getPercent(double tps){
|
||||
double finalPercent = Math.min(Math.round(100 / 20.0 * tps), 100.0);
|
||||
|
||||
return (tps > 20.0 ? "*" : "") + finalPercent + "%";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
package com.extendedclip.papi.expansion.server;
|
||||
|
||||
import me.clip.placeholderapi.PlaceholderAPIPlugin;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public final class ServerUtils {
|
||||
|
||||
private String version = null;
|
||||
private String build = null;
|
||||
private String variant = null;
|
||||
|
||||
private final Map<String, String> variants = new LinkedHashMap<>();
|
||||
|
||||
private Object craftServer = null;
|
||||
private Field tps = null;
|
||||
|
||||
private boolean hasTpsMethod = false;
|
||||
|
||||
public ServerUtils() {
|
||||
variants.put("io.papermc.paper.threadedregions.RegionizedServer", "Folia");
|
||||
variants.put("net.pl3x.purpur.PurpurConfig", "Purpur");
|
||||
variants.put("gg.airplane.AirplaneConfig", "Airplane");
|
||||
variants.put("com.tuinity.tuinity.config.TuinityConfig", "Tuinity");
|
||||
variants.put("io.papermc.paper.configuration.ConfigurationLoaders", "Paper"); // New config location for Paper 1.19+
|
||||
variants.put("com.destroystokyo.paper.PaperConfig", "Paper"); // Still supported by Paper, but deprecated.
|
||||
variants.put("org.spigotmc.SpigotConfig", "Spigot");
|
||||
|
||||
resolveTPSHandler();
|
||||
}
|
||||
|
||||
public String getServerVariant() {
|
||||
if (variant != null) {
|
||||
return variant;
|
||||
}
|
||||
|
||||
for(Map.Entry<String, String> variant : variants.entrySet()) {
|
||||
try {
|
||||
Class.forName(variant.getKey());
|
||||
|
||||
return (this.variant = variant.getValue());
|
||||
} catch (ClassNotFoundException ignored) {}
|
||||
}
|
||||
|
||||
return (this.variant = "Unknown");
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
if (version != null) {
|
||||
return version;
|
||||
}
|
||||
|
||||
return (version = Bukkit.getBukkitVersion().split("-")[0]);
|
||||
}
|
||||
|
||||
public String getBuild() {
|
||||
if (build != null) {
|
||||
return build;
|
||||
}
|
||||
|
||||
String[] buildParts = Bukkit.getVersion().split("-");
|
||||
switch (getServerVariant().toLowerCase(Locale.ROOT)) {
|
||||
case "spigot":
|
||||
// TODO: Find out what those variants return.
|
||||
case "tuinity":
|
||||
case "airplane":
|
||||
case "purpur":
|
||||
return (build = buildParts[0]);
|
||||
|
||||
case "paper":
|
||||
if (buildParts.length >= 3) {
|
||||
if (buildParts[2].contains(" ")) {
|
||||
return (build = buildParts[2].substring(0, buildParts[2].indexOf(" ")));
|
||||
} else {
|
||||
return (build = buildParts[2]);
|
||||
}
|
||||
} else {
|
||||
return (build = "Unknown");
|
||||
}
|
||||
|
||||
default:
|
||||
return (build = "Unknown");
|
||||
}
|
||||
}
|
||||
|
||||
public double[] getTps() {
|
||||
if (hasTpsMethod) {
|
||||
return Bukkit.getTPS();
|
||||
}
|
||||
|
||||
if (craftServer == null || tps == null) {
|
||||
return new double[]{0, 0, 0};
|
||||
}
|
||||
|
||||
try {
|
||||
return (double[]) tps.get(craftServer);
|
||||
} catch (IllegalAccessException ignored) {
|
||||
return new double[]{0, 0, 0};
|
||||
}
|
||||
}
|
||||
|
||||
private void resolveTPSHandler() {
|
||||
try {
|
||||
// If this throws is the server not a fork...
|
||||
Bukkit.class.getMethod("getTPS");
|
||||
hasTpsMethod = true;
|
||||
} catch (NoSuchMethodException ignored) {
|
||||
final String mcVersion = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
|
||||
|
||||
try {
|
||||
if (getMajorVersion() >= 17) {
|
||||
craftServer = Class.forName("net.minecraft.server.MinecraftServer")
|
||||
.getMethod("getServer").invoke(null);
|
||||
} else {
|
||||
craftServer = Class.forName("net.minecraft.server." + mcVersion + ".MinecraftServer")
|
||||
.getMethod("getServer").invoke(null);
|
||||
}
|
||||
|
||||
tps = craftServer.getClass().getField("recentTps");
|
||||
} catch (Exception ex) {
|
||||
PlaceholderAPIPlugin.getInstance().getLogger().warning("Could not resolve TPS handling!");
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int getMajorVersion() {
|
||||
final Matcher matcher = Pattern.compile("\\(MC: (\\d)\\.(\\d+)\\.?(\\d+?)?\\)").matcher(Bukkit.getVersion());
|
||||
if (matcher.find()) {
|
||||
try{
|
||||
return Integer.parseInt(matcher.toMatchResult().group(2), 10);
|
||||
} catch (NumberFormatException ignored) {
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user