mirror of
https://github.com/PurpurMC/Purpur.git
synced 2026-02-17 16:37:43 +01:00
1014 lines
39 KiB
Diff
1014 lines
39 KiB
Diff
From 350893286175492ce9dd02d807bb093b15bbd310 Mon Sep 17 00:00:00 2001
|
|
From: William Blake Galbreath <blake.galbreath@gmail.com>
|
|
Date: Thu, 9 May 2019 18:09:43 -0500
|
|
Subject: [PATCH] Purpur config files
|
|
|
|
---
|
|
.../com/destroystokyo/paper/PaperConfig.java | 2 +
|
|
.../net/minecraft/server/DedicatedServer.java | 9 +
|
|
src/main/java/net/minecraft/server/World.java | 3 +
|
|
src/main/java/net/pl3x/purpur/Metrics.java | 598 ++++++++++++++++++
|
|
.../java/net/pl3x/purpur/PurpurCommand.java | 66 ++
|
|
.../java/net/pl3x/purpur/PurpurConfig.java | 133 ++++
|
|
.../net/pl3x/purpur/PurpurWorldConfig.java | 63 ++
|
|
.../org/bukkit/craftbukkit/CraftServer.java | 3 +
|
|
.../java/org/bukkit/craftbukkit/Main.java | 8 +
|
|
9 files changed, 885 insertions(+)
|
|
create mode 100644 src/main/java/net/pl3x/purpur/Metrics.java
|
|
create mode 100644 src/main/java/net/pl3x/purpur/PurpurCommand.java
|
|
create mode 100644 src/main/java/net/pl3x/purpur/PurpurConfig.java
|
|
create mode 100644 src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
|
|
|
|
diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java
|
|
index 1d7d1ffbf7..d886f1d145 100644
|
|
--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
|
|
+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
|
|
@@ -96,10 +96,12 @@ public class PaperConfig {
|
|
MinecraftServer.getServer().server.getCommandMap().register(entry.getKey(), "Paper", entry.getValue());
|
|
}
|
|
|
|
+ /* // Purpur - Replace with our own
|
|
if (!metricsStarted) {
|
|
Metrics.PaperMetrics.startMetrics();
|
|
metricsStarted = true;
|
|
}
|
|
+ */ // Purpur end
|
|
}
|
|
|
|
static void readConfig(Class<?> clazz, Object instance) {
|
|
diff --git a/src/main/java/net/minecraft/server/DedicatedServer.java b/src/main/java/net/minecraft/server/DedicatedServer.java
|
|
index 3ed74ae0ec..8139108470 100644
|
|
--- a/src/main/java/net/minecraft/server/DedicatedServer.java
|
|
+++ b/src/main/java/net/minecraft/server/DedicatedServer.java
|
|
@@ -184,6 +184,15 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer
|
|
return false;
|
|
}
|
|
com.destroystokyo.paper.PaperConfig.registerCommands();
|
|
+ // Purpur start
|
|
+ try {
|
|
+ net.pl3x.purpur.PurpurConfig.init((File) options.valueOf("purpur-settings"));
|
|
+ } catch (Exception e) {
|
|
+ DedicatedServer.LOGGER.error("Unable to load server configuration", e);
|
|
+ return false;
|
|
+ }
|
|
+ net.pl3x.purpur.PurpurConfig.registerCommands();
|
|
+ // Purpur end
|
|
com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // load version history now
|
|
// Paper end
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
|
|
index 985f303726..46b0c68207 100644
|
|
--- a/src/main/java/net/minecraft/server/World.java
|
|
+++ b/src/main/java/net/minecraft/server/World.java
|
|
@@ -96,6 +96,8 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
|
|
public final com.destroystokyo.paper.PaperWorldConfig paperConfig; // Paper
|
|
public final ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray
|
|
|
|
+ public final net.pl3x.purpur.PurpurWorldConfig purpurConfig; // Purpur
|
|
+
|
|
public final co.aikar.timings.WorldTimingsHandler timings; // Paper
|
|
public static BlockPosition lastPhysicsProblem; // Spigot
|
|
private org.spigotmc.TickLimiter entityLimiter;
|
|
@@ -137,6 +139,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
|
|
this.spigotConfig = new org.spigotmc.SpigotWorldConfig( worlddata.getName() ); // Spigot
|
|
this.paperConfig = new com.destroystokyo.paper.PaperWorldConfig(worlddata.getName(), this.spigotConfig); // Paper
|
|
this.chunkPacketBlockController = this.paperConfig.antiXray ? new ChunkPacketBlockControllerAntiXray(this.paperConfig) : ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray
|
|
+ this.purpurConfig = new net.pl3x.purpur.PurpurWorldConfig(worlddata.getName(), this.paperConfig, this.spigotConfig); // Purpur
|
|
this.generator = gen;
|
|
this.world = new CraftWorld((WorldServer) this, gen, env);
|
|
this.ticksPerAnimalSpawns = this.getServer().getTicksPerAnimalSpawns(); // CraftBukkit
|
|
diff --git a/src/main/java/net/pl3x/purpur/Metrics.java b/src/main/java/net/pl3x/purpur/Metrics.java
|
|
new file mode 100644
|
|
index 0000000000..246eb8140f
|
|
--- /dev/null
|
|
+++ b/src/main/java/net/pl3x/purpur/Metrics.java
|
|
@@ -0,0 +1,598 @@
|
|
+package net.pl3x.purpur;
|
|
+
|
|
+import com.google.gson.JsonArray;
|
|
+import com.google.gson.JsonObject;
|
|
+import net.minecraft.server.MinecraftServer;
|
|
+import org.bukkit.Bukkit;
|
|
+import org.bukkit.configuration.file.YamlConfiguration;
|
|
+
|
|
+import javax.net.ssl.HttpsURLConnection;
|
|
+import java.io.ByteArrayOutputStream;
|
|
+import java.io.DataOutputStream;
|
|
+import java.io.File;
|
|
+import java.io.IOException;
|
|
+import java.net.URL;
|
|
+import java.nio.charset.StandardCharsets;
|
|
+import java.util.ArrayList;
|
|
+import java.util.HashMap;
|
|
+import java.util.List;
|
|
+import java.util.Map;
|
|
+import java.util.Timer;
|
|
+import java.util.TimerTask;
|
|
+import java.util.UUID;
|
|
+import java.util.concurrent.Callable;
|
|
+import java.util.logging.Level;
|
|
+import java.util.logging.Logger;
|
|
+import java.util.regex.Matcher;
|
|
+import java.util.regex.Pattern;
|
|
+import java.util.zip.GZIPOutputStream;
|
|
+
|
|
+/**
|
|
+ * bStats collects some data for plugin authors.
|
|
+ * <p>
|
|
+ * Check out https://bStats.org/ to learn more about bStats!
|
|
+ */
|
|
+public class Metrics {
|
|
+ public static final int B_STATS_VERSION = 1;
|
|
+ private static final String URL = "https://bStats.org/submitData/server-implementation";
|
|
+ private static boolean logFailedRequests = false;
|
|
+ private static Logger logger = Logger.getLogger("bStats");
|
|
+ private final String name;
|
|
+ private final String serverUUID;
|
|
+ private final List<CustomChart> charts = new ArrayList<>();
|
|
+
|
|
+ /**
|
|
+ * Class constructor.
|
|
+ *
|
|
+ * @param name The name of the server software.
|
|
+ * @param serverUUID The uuid of the server.
|
|
+ * @param logFailedRequests Whether failed requests should be logged or not.
|
|
+ * @param logger The logger for the failed requests.
|
|
+ */
|
|
+ public Metrics(String name, String serverUUID, boolean logFailedRequests, Logger logger) {
|
|
+ this.name = name;
|
|
+ this.serverUUID = serverUUID;
|
|
+ Metrics.logFailedRequests = logFailedRequests;
|
|
+ Metrics.logger = logger;
|
|
+
|
|
+ startSubmitting();
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Adds a custom chart.
|
|
+ *
|
|
+ * @param chart The chart to add.
|
|
+ */
|
|
+ public void addCustomChart(CustomChart chart) {
|
|
+ if (chart == null) {
|
|
+ throw new IllegalArgumentException("Chart cannot be null!");
|
|
+ }
|
|
+ charts.add(chart);
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Starts the Scheduler which submits our data every 30 minutes.
|
|
+ */
|
|
+ private void startSubmitting() {
|
|
+ final Timer timer = new Timer(true);
|
|
+ timer.scheduleAtFixedRate(new TimerTask() {
|
|
+ @Override
|
|
+ public void run() {
|
|
+ submitData();
|
|
+ }
|
|
+ }, 1000 * 60 * 5, 1000 * 60 * 30);
|
|
+ // Submit the data every 30 minutes, first time after 5 minutes to give other plugins enough time to start
|
|
+ // WARNING: Changing the frequency has no effect but your plugin WILL be blocked/deleted!
|
|
+ // WARNING: Just don't do it!
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Gets the plugin specific data.
|
|
+ * This method is called using Reflection.
|
|
+ *
|
|
+ * @return The plugin specific data.
|
|
+ */
|
|
+ public JsonObject getPluginData() {
|
|
+ JsonObject data = new JsonObject();
|
|
+
|
|
+ data.addProperty("pluginName", name); // Append the name of the plugin
|
|
+
|
|
+ JsonArray customCharts = new JsonArray();
|
|
+ for (CustomChart customChart : charts) {
|
|
+ JsonObject chart = customChart.getRequestJsonObject();
|
|
+ if (chart != null) {
|
|
+ customCharts.add(chart);
|
|
+ }
|
|
+ }
|
|
+ data.add("customCharts", customCharts);
|
|
+
|
|
+ return data;
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Gets the server specific data.
|
|
+ *
|
|
+ * @return The server specific data.
|
|
+ */
|
|
+ private JsonObject getServerData() {
|
|
+ JsonObject data = new JsonObject();
|
|
+
|
|
+ data.addProperty("serverUUID", serverUUID);
|
|
+
|
|
+ data.addProperty("osName", System.getProperty("os.name"));
|
|
+ data.addProperty("osArch", System.getProperty("os.arch"));
|
|
+ data.addProperty("osVersion", System.getProperty("os.version"));
|
|
+ data.addProperty("coreCount", Runtime.getRuntime().availableProcessors());
|
|
+
|
|
+ return data;
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Collects the data and sends it afterwards.
|
|
+ */
|
|
+ private void submitData() {
|
|
+ final JsonObject data = getServerData();
|
|
+
|
|
+ JsonArray pluginData = new JsonArray();
|
|
+ pluginData.add(getPluginData());
|
|
+ data.add("plugins", pluginData);
|
|
+
|
|
+ try {
|
|
+ sendData(data);
|
|
+ } catch (Exception e) {
|
|
+ if (logFailedRequests) {
|
|
+ logger.log(Level.WARNING, "Could not submit plugin stats of " + name, e);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Sends the data to the bStats server.
|
|
+ *
|
|
+ * @param data The data to send.
|
|
+ * @throws Exception If the request failed.
|
|
+ */
|
|
+ private static void sendData(JsonObject data) throws Exception {
|
|
+ if (data == null) {
|
|
+ throw new IllegalArgumentException("Data cannot be null!");
|
|
+ }
|
|
+ HttpsURLConnection connection = (HttpsURLConnection) new URL(URL).openConnection();
|
|
+
|
|
+ // Compress the data to save bandwidth
|
|
+ byte[] compressedData = compress(data.toString());
|
|
+
|
|
+ // Add headers
|
|
+ connection.setRequestMethod("POST");
|
|
+ connection.addRequestProperty("Accept", "application/json");
|
|
+ connection.addRequestProperty("Connection", "close");
|
|
+ connection.addRequestProperty("Content-Encoding", "gzip"); // We gzip our request
|
|
+ connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length));
|
|
+ connection.setRequestProperty("Content-Type", "application/json"); // We send our data in JSON format
|
|
+ connection.setRequestProperty("User-Agent", "MC-Server/" + B_STATS_VERSION);
|
|
+
|
|
+ // Send data
|
|
+ connection.setDoOutput(true);
|
|
+ DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream());
|
|
+ outputStream.write(compressedData);
|
|
+ outputStream.flush();
|
|
+ outputStream.close();
|
|
+
|
|
+ connection.getInputStream().close();
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Gzips the given String.
|
|
+ *
|
|
+ * @param str The string to gzip.
|
|
+ * @return The gzipped String.
|
|
+ * @throws IOException If the compression failed.
|
|
+ */
|
|
+ private static byte[] compress(final String str) throws IOException {
|
|
+ if (str == null) {
|
|
+ return null;
|
|
+ }
|
|
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
|
+ GZIPOutputStream gzip = new GZIPOutputStream(outputStream);
|
|
+ gzip.write(str.getBytes(StandardCharsets.UTF_8));
|
|
+ gzip.close();
|
|
+ return outputStream.toByteArray();
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Represents a custom chart.
|
|
+ */
|
|
+ public static abstract class CustomChart {
|
|
+
|
|
+ // The id of the chart
|
|
+ final String chartId;
|
|
+
|
|
+ /**
|
|
+ * Class constructor.
|
|
+ *
|
|
+ * @param chartId The id of the chart.
|
|
+ */
|
|
+ CustomChart(String chartId) {
|
|
+ if (chartId == null || chartId.isEmpty()) {
|
|
+ throw new IllegalArgumentException("ChartId cannot be null or empty!");
|
|
+ }
|
|
+ this.chartId = chartId;
|
|
+ }
|
|
+
|
|
+ private JsonObject getRequestJsonObject() {
|
|
+ JsonObject chart = new JsonObject();
|
|
+ chart.addProperty("chartId", chartId);
|
|
+ try {
|
|
+ JsonObject data = getChartData();
|
|
+ if (data == null) {
|
|
+ // If the data is null we don't send the chart.
|
|
+ return null;
|
|
+ }
|
|
+ chart.add("data", data);
|
|
+ } catch (Throwable t) {
|
|
+ if (logFailedRequests) {
|
|
+ Bukkit.getLogger().log(Level.WARNING, "Failed to get data for custom chart with id " + chartId, t);
|
|
+ }
|
|
+ return null;
|
|
+ }
|
|
+ return chart;
|
|
+ }
|
|
+
|
|
+ protected abstract JsonObject getChartData() throws Exception;
|
|
+
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Represents a custom simple pie.
|
|
+ */
|
|
+ public static class SimplePie extends CustomChart {
|
|
+
|
|
+ private final Callable<String> callable;
|
|
+
|
|
+ /**
|
|
+ * Class constructor.
|
|
+ *
|
|
+ * @param chartId The id of the chart.
|
|
+ * @param callable The callable which is used to request the chart data.
|
|
+ */
|
|
+ public SimplePie(String chartId, Callable<String> callable) {
|
|
+ super(chartId);
|
|
+ this.callable = callable;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected JsonObject getChartData() throws Exception {
|
|
+ JsonObject data = new JsonObject();
|
|
+ String value = callable.call();
|
|
+ if (value == null || value.isEmpty()) {
|
|
+ // Null = skip the chart
|
|
+ return null;
|
|
+ }
|
|
+ data.addProperty("value", value);
|
|
+ return data;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Represents a custom advanced pie.
|
|
+ */
|
|
+ public static class AdvancedPie extends CustomChart {
|
|
+
|
|
+ private final Callable<Map<String, Integer>> callable;
|
|
+
|
|
+ /**
|
|
+ * Class constructor.
|
|
+ *
|
|
+ * @param chartId The id of the chart.
|
|
+ * @param callable The callable which is used to request the chart data.
|
|
+ */
|
|
+ public AdvancedPie(String chartId, Callable<Map<String, Integer>> callable) {
|
|
+ super(chartId);
|
|
+ this.callable = callable;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected JsonObject getChartData() throws Exception {
|
|
+ JsonObject data = new JsonObject();
|
|
+ JsonObject values = new JsonObject();
|
|
+ Map<String, Integer> map = callable.call();
|
|
+ if (map == null || map.isEmpty()) {
|
|
+ // Null = skip the chart
|
|
+ return null;
|
|
+ }
|
|
+ boolean allSkipped = true;
|
|
+ for (Map.Entry<String, Integer> entry : map.entrySet()) {
|
|
+ if (entry.getValue() == 0) {
|
|
+ continue; // Skip this invalid
|
|
+ }
|
|
+ allSkipped = false;
|
|
+ values.addProperty(entry.getKey(), entry.getValue());
|
|
+ }
|
|
+ if (allSkipped) {
|
|
+ // Null = skip the chart
|
|
+ return null;
|
|
+ }
|
|
+ data.add("values", values);
|
|
+ return data;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Represents a custom drilldown pie.
|
|
+ */
|
|
+ public static class DrilldownPie extends CustomChart {
|
|
+
|
|
+ private final Callable<Map<String, Map<String, Integer>>> callable;
|
|
+
|
|
+ /**
|
|
+ * Class constructor.
|
|
+ *
|
|
+ * @param chartId The id of the chart.
|
|
+ * @param callable The callable which is used to request the chart data.
|
|
+ */
|
|
+ public DrilldownPie(String chartId, Callable<Map<String, Map<String, Integer>>> callable) {
|
|
+ super(chartId);
|
|
+ this.callable = callable;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public JsonObject getChartData() throws Exception {
|
|
+ JsonObject data = new JsonObject();
|
|
+ JsonObject values = new JsonObject();
|
|
+ Map<String, Map<String, Integer>> map = callable.call();
|
|
+ if (map == null || map.isEmpty()) {
|
|
+ // Null = skip the chart
|
|
+ return null;
|
|
+ }
|
|
+ boolean reallyAllSkipped = true;
|
|
+ for (Map.Entry<String, Map<String, Integer>> entryValues : map.entrySet()) {
|
|
+ JsonObject value = new JsonObject();
|
|
+ boolean allSkipped = true;
|
|
+ for (Map.Entry<String, Integer> valueEntry : map.get(entryValues.getKey()).entrySet()) {
|
|
+ value.addProperty(valueEntry.getKey(), valueEntry.getValue());
|
|
+ allSkipped = false;
|
|
+ }
|
|
+ if (!allSkipped) {
|
|
+ reallyAllSkipped = false;
|
|
+ values.add(entryValues.getKey(), value);
|
|
+ }
|
|
+ }
|
|
+ if (reallyAllSkipped) {
|
|
+ // Null = skip the chart
|
|
+ return null;
|
|
+ }
|
|
+ data.add("values", values);
|
|
+ return data;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Represents a custom single line chart.
|
|
+ */
|
|
+ public static class SingleLineChart extends CustomChart {
|
|
+
|
|
+ private final Callable<Integer> callable;
|
|
+
|
|
+ /**
|
|
+ * Class constructor.
|
|
+ *
|
|
+ * @param chartId The id of the chart.
|
|
+ * @param callable The callable which is used to request the chart data.
|
|
+ */
|
|
+ public SingleLineChart(String chartId, Callable<Integer> callable) {
|
|
+ super(chartId);
|
|
+ this.callable = callable;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected JsonObject getChartData() throws Exception {
|
|
+ JsonObject data = new JsonObject();
|
|
+ int value = callable.call();
|
|
+ if (value == 0) {
|
|
+ // Null = skip the chart
|
|
+ return null;
|
|
+ }
|
|
+ data.addProperty("value", value);
|
|
+ return data;
|
|
+ }
|
|
+
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Represents a custom multi line chart.
|
|
+ */
|
|
+ public static class MultiLineChart extends CustomChart {
|
|
+
|
|
+ private final Callable<Map<String, Integer>> callable;
|
|
+
|
|
+ /**
|
|
+ * Class constructor.
|
|
+ *
|
|
+ * @param chartId The id of the chart.
|
|
+ * @param callable The callable which is used to request the chart data.
|
|
+ */
|
|
+ public MultiLineChart(String chartId, Callable<Map<String, Integer>> callable) {
|
|
+ super(chartId);
|
|
+ this.callable = callable;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected JsonObject getChartData() throws Exception {
|
|
+ JsonObject data = new JsonObject();
|
|
+ JsonObject values = new JsonObject();
|
|
+ Map<String, Integer> map = callable.call();
|
|
+ if (map == null || map.isEmpty()) {
|
|
+ // Null = skip the chart
|
|
+ return null;
|
|
+ }
|
|
+ boolean allSkipped = true;
|
|
+ for (Map.Entry<String, Integer> entry : map.entrySet()) {
|
|
+ if (entry.getValue() == 0) {
|
|
+ continue; // Skip this invalid
|
|
+ }
|
|
+ allSkipped = false;
|
|
+ values.addProperty(entry.getKey(), entry.getValue());
|
|
+ }
|
|
+ if (allSkipped) {
|
|
+ // Null = skip the chart
|
|
+ return null;
|
|
+ }
|
|
+ data.add("values", values);
|
|
+ return data;
|
|
+ }
|
|
+
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Represents a custom simple bar chart.
|
|
+ */
|
|
+ public static class SimpleBarChart extends CustomChart {
|
|
+
|
|
+ private final Callable<Map<String, Integer>> callable;
|
|
+
|
|
+ /**
|
|
+ * Class constructor.
|
|
+ *
|
|
+ * @param chartId The id of the chart.
|
|
+ * @param callable The callable which is used to request the chart data.
|
|
+ */
|
|
+ public SimpleBarChart(String chartId, Callable<Map<String, Integer>> callable) {
|
|
+ super(chartId);
|
|
+ this.callable = callable;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected JsonObject getChartData() throws Exception {
|
|
+ JsonObject data = new JsonObject();
|
|
+ JsonObject values = new JsonObject();
|
|
+ Map<String, Integer> map = callable.call();
|
|
+ if (map == null || map.isEmpty()) {
|
|
+ // Null = skip the chart
|
|
+ return null;
|
|
+ }
|
|
+ for (Map.Entry<String, Integer> entry : map.entrySet()) {
|
|
+ JsonArray categoryValues = new JsonArray();
|
|
+ categoryValues.add(entry.getValue());
|
|
+ values.add(entry.getKey(), categoryValues);
|
|
+ }
|
|
+ data.add("values", values);
|
|
+ return data;
|
|
+ }
|
|
+
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Represents a custom advanced bar chart.
|
|
+ */
|
|
+ public static class AdvancedBarChart extends CustomChart {
|
|
+
|
|
+ private final Callable<Map<String, int[]>> callable;
|
|
+
|
|
+ /**
|
|
+ * Class constructor.
|
|
+ *
|
|
+ * @param chartId The id of the chart.
|
|
+ * @param callable The callable which is used to request the chart data.
|
|
+ */
|
|
+ public AdvancedBarChart(String chartId, Callable<Map<String, int[]>> callable) {
|
|
+ super(chartId);
|
|
+ this.callable = callable;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected JsonObject getChartData() throws Exception {
|
|
+ JsonObject data = new JsonObject();
|
|
+ JsonObject values = new JsonObject();
|
|
+ Map<String, int[]> map = callable.call();
|
|
+ if (map == null || map.isEmpty()) {
|
|
+ // Null = skip the chart
|
|
+ return null;
|
|
+ }
|
|
+ boolean allSkipped = true;
|
|
+ for (Map.Entry<String, int[]> entry : map.entrySet()) {
|
|
+ if (entry.getValue().length == 0) {
|
|
+ continue; // Skip this invalid
|
|
+ }
|
|
+ allSkipped = false;
|
|
+ JsonArray categoryValues = new JsonArray();
|
|
+ for (int categoryValue : entry.getValue()) {
|
|
+ categoryValues.add(categoryValue);
|
|
+ }
|
|
+ values.add(entry.getKey(), categoryValues);
|
|
+ }
|
|
+ if (allSkipped) {
|
|
+ // Null = skip the chart
|
|
+ return null;
|
|
+ }
|
|
+ data.add("values", values);
|
|
+ return data;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ static class PurpurMetrics {
|
|
+ static void startMetrics() {
|
|
+ File configFile = new File(new File((File) MinecraftServer.getServer().options.valueOf("plugins"), "bStats"), "config.yml");
|
|
+ YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile);
|
|
+ if (!config.isSet("serverUuid")) {
|
|
+ config.addDefault("enabled", true);
|
|
+ config.addDefault("serverUuid", UUID.randomUUID().toString());
|
|
+ config.addDefault("logFailedRequests", false);
|
|
+ config.options().header(
|
|
+ "bStats collects some data for plugin authors like how many servers are using their plugins.\n" +
|
|
+ "To honor their work, you should not disable it.\n" +
|
|
+ "This has nearly no effect on the server performance!\n" +
|
|
+ "Check out https://bStats.org/ to learn more :)"
|
|
+ ).copyDefaults(true);
|
|
+ try {
|
|
+ config.save(configFile);
|
|
+ } catch (IOException ignored) {
|
|
+ }
|
|
+ }
|
|
+ String serverUUID = config.getString("serverUuid");
|
|
+ boolean logFailedRequests = config.getBoolean("logFailedRequests", false);
|
|
+ if (config.getBoolean("enabled", true)) {
|
|
+ Metrics metrics = new Metrics("Purpur", serverUUID, logFailedRequests, Bukkit.getLogger());
|
|
+
|
|
+ metrics.addCustomChart(new Metrics.SimplePie("minecraft_version", () -> {
|
|
+ String minecraftVersion = Bukkit.getVersion();
|
|
+ minecraftVersion = minecraftVersion.substring(minecraftVersion.indexOf("MC: ") + 4, minecraftVersion.length() - 1);
|
|
+ return minecraftVersion;
|
|
+ }));
|
|
+
|
|
+ metrics.addCustomChart(new Metrics.SingleLineChart("players", () -> Bukkit.getOnlinePlayers().size()));
|
|
+ metrics.addCustomChart(new Metrics.SimplePie("online_mode", () -> Bukkit.getOnlineMode() ? "online" : "offline"));
|
|
+ metrics.addCustomChart(new Metrics.SimplePie("purpur_version", () -> (Metrics.class.getPackage().getImplementationVersion() != null) ? Metrics.class.getPackage().getImplementationVersion() : "unknown"));
|
|
+
|
|
+ metrics.addCustomChart(new Metrics.DrilldownPie("java_version", () -> {
|
|
+ Map<String, Map<String, Integer>> map = new HashMap<>();
|
|
+ String javaVersion = System.getProperty("java.version");
|
|
+ Map<String, Integer> entry = new HashMap<>();
|
|
+ entry.put(javaVersion, 1);
|
|
+
|
|
+ // http://openjdk.java.net/jeps/223
|
|
+ // Java decided to change their versioning scheme and in doing so modified the java.version system
|
|
+ // property to return $major[.$minor][.$secuity][-ea], as opposed to 1.$major.0_$identifier
|
|
+ // we can handle pre-9 by checking if the "major" is equal to "1", otherwise, 9+
|
|
+ String majorVersion = javaVersion.split("\\.")[0];
|
|
+ String release;
|
|
+
|
|
+ int indexOf = javaVersion.lastIndexOf('.');
|
|
+
|
|
+ if (majorVersion.equals("1")) {
|
|
+ release = "Java " + javaVersion.substring(0, indexOf);
|
|
+ } else {
|
|
+ // of course, it really wouldn't be all that simple if they didn't add a quirk, now would it
|
|
+ // valid strings for the major may potentially include values such as -ea to deannotate a pre release
|
|
+ Matcher versionMatcher = Pattern.compile("\\d+").matcher(majorVersion);
|
|
+ if (versionMatcher.find()) {
|
|
+ majorVersion = versionMatcher.group(0);
|
|
+ }
|
|
+ release = "Java " + majorVersion;
|
|
+ }
|
|
+ map.put(release, entry);
|
|
+
|
|
+ return map;
|
|
+ }));
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/net/pl3x/purpur/PurpurCommand.java b/src/main/java/net/pl3x/purpur/PurpurCommand.java
|
|
new file mode 100644
|
|
index 0000000000..f8cf4ad234
|
|
--- /dev/null
|
|
+++ b/src/main/java/net/pl3x/purpur/PurpurCommand.java
|
|
@@ -0,0 +1,66 @@
|
|
+package net.pl3x.purpur;
|
|
+
|
|
+import net.minecraft.server.MinecraftServer;
|
|
+import net.minecraft.server.WorldServer;
|
|
+import org.bukkit.ChatColor;
|
|
+import org.bukkit.Location;
|
|
+import org.bukkit.command.Command;
|
|
+import org.bukkit.command.CommandSender;
|
|
+
|
|
+import java.io.File;
|
|
+import java.util.Arrays;
|
|
+import java.util.Collections;
|
|
+import java.util.List;
|
|
+import java.util.stream.Collectors;
|
|
+
|
|
+public class PurpurCommand extends Command {
|
|
+ public PurpurCommand(String name) {
|
|
+ super(name);
|
|
+ this.description = "Purpur related commands";
|
|
+ this.usageMessage = "/purpur [reload | version]";
|
|
+ this.setPermission("bukkit.command.purpur");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public List<String> tabComplete(CommandSender sender, String alias, String[] args, Location location) throws IllegalArgumentException {
|
|
+ if (args.length == 1) {
|
|
+ return Arrays.asList("reload", "version").stream()
|
|
+ .filter(arg -> arg.startsWith(args[0].toLowerCase()))
|
|
+ .collect(Collectors.toList());
|
|
+ }
|
|
+ return Collections.emptyList();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean execute(CommandSender sender, String commandLabel, String[] args) {
|
|
+ if (!testPermission(sender)) return true;
|
|
+
|
|
+ if (args.length != 1) {
|
|
+ sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage);
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ if (args[0].equalsIgnoreCase("reload")) {
|
|
+ Command.broadcastCommandMessage(sender, ChatColor.RED + "Please note that this command is not supported and may cause issues.");
|
|
+ Command.broadcastCommandMessage(sender, ChatColor.RED + "If you encounter any issues please use the /stop command to restart your server.");
|
|
+
|
|
+ MinecraftServer console = MinecraftServer.getServer();
|
|
+ net.pl3x.purpur.PurpurConfig.init((File) console.options.valueOf("purpur-settings"));
|
|
+ for (WorldServer world : console.getWorlds()) {
|
|
+ world.purpurConfig.init();
|
|
+ }
|
|
+ console.server.reloadCount++;
|
|
+
|
|
+ Command.broadcastCommandMessage(sender, ChatColor.GREEN + "Purpur config reload complete.");
|
|
+ }
|
|
+
|
|
+ else if (args[0].equalsIgnoreCase("version")) {
|
|
+ Command verCmd = org.bukkit.Bukkit.getServer().getCommandMap().getCommand("version");
|
|
+ if (verCmd != null) {
|
|
+ return verCmd.execute(sender, commandLabel, new String[0]);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return true;
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/net/pl3x/purpur/PurpurConfig.java b/src/main/java/net/pl3x/purpur/PurpurConfig.java
|
|
new file mode 100644
|
|
index 0000000000..6f378b5f69
|
|
--- /dev/null
|
|
+++ b/src/main/java/net/pl3x/purpur/PurpurConfig.java
|
|
@@ -0,0 +1,133 @@
|
|
+package net.pl3x.purpur;
|
|
+
|
|
+import com.google.common.base.Throwables;
|
|
+import net.minecraft.server.MinecraftServer;
|
|
+import org.bukkit.Bukkit;
|
|
+import org.bukkit.command.Command;
|
|
+import org.bukkit.configuration.InvalidConfigurationException;
|
|
+import org.bukkit.configuration.file.YamlConfiguration;
|
|
+
|
|
+import java.io.File;
|
|
+import java.io.IOException;
|
|
+import java.lang.reflect.InvocationTargetException;
|
|
+import java.lang.reflect.Method;
|
|
+import java.lang.reflect.Modifier;
|
|
+import java.util.HashMap;
|
|
+import java.util.List;
|
|
+import java.util.Map;
|
|
+import java.util.logging.Level;
|
|
+
|
|
+public class PurpurConfig {
|
|
+ private static final int CONFIG_VERSION = 2;
|
|
+ private static final String HEADER = "This is the main configuration file for Purpur.\n"
|
|
+ + "As you can see, there's tons to configure. Some options may impact gameplay, so use\n"
|
|
+ + "with caution, and make sure you know what each option does before configuring.\n"
|
|
+ + "\n"
|
|
+ + "If you need help with the configuration or have any questions related to Purpur,\n"
|
|
+ + "join us in our Discord guild.\n"
|
|
+ + "\n"
|
|
+ + "Website: https://pl3x.net \n"
|
|
+ + "Docs: https://purpur.readthedocs.org/ \n";
|
|
+ private static File CONFIG_FILE;
|
|
+ static YamlConfiguration config;
|
|
+ static Map<String, Command> commands;
|
|
+ private static int version;
|
|
+ private static boolean verbose;
|
|
+ private static boolean metricsStarted;
|
|
+
|
|
+ public static void init(File configFile) {
|
|
+ CONFIG_FILE = configFile;
|
|
+ config = new YamlConfiguration();
|
|
+ try {
|
|
+ config.load(CONFIG_FILE);
|
|
+ } catch (IOException ignore) {
|
|
+ } catch (InvalidConfigurationException ex) {
|
|
+ Bukkit.getLogger().log(Level.SEVERE, "Could not load purpur.yml, please correct your syntax errors", ex);
|
|
+ throw Throwables.propagate(ex);
|
|
+ }
|
|
+ config.options().header(HEADER);
|
|
+ config.options().copyDefaults(true);
|
|
+ verbose = getBoolean("verbose", false);
|
|
+
|
|
+ commands = new HashMap<>();
|
|
+ commands.put("purpur", new PurpurCommand("purpur"));
|
|
+
|
|
+ version = getInt("config-version", CONFIG_VERSION);
|
|
+ set("config-version", version);
|
|
+
|
|
+ readConfig(PurpurConfig.class, null);
|
|
+ }
|
|
+
|
|
+ protected static void log(String s) {
|
|
+ log(Level.INFO, s);
|
|
+ }
|
|
+
|
|
+ protected static void log(Level level, String s) {
|
|
+ if (verbose) {
|
|
+ Bukkit.getLogger().log(level, s);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public static void registerCommands() {
|
|
+ for (Map.Entry<String, Command> entry : commands.entrySet()) {
|
|
+ MinecraftServer.getServer().server.getCommandMap().register(entry.getKey(), "Purpur", entry.getValue());
|
|
+ }
|
|
+
|
|
+ if (!metricsStarted) {
|
|
+ Metrics.PurpurMetrics.startMetrics();
|
|
+ metricsStarted = true;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ static void readConfig(Class<?> clazz, Object instance) {
|
|
+ for (Method method : clazz.getDeclaredMethods()) {
|
|
+ if (Modifier.isPrivate(method.getModifiers())) {
|
|
+ if (method.getParameterTypes().length == 0 && method.getReturnType() == Void.TYPE) {
|
|
+ try {
|
|
+ method.setAccessible(true);
|
|
+ method.invoke(instance);
|
|
+ } catch (InvocationTargetException ex) {
|
|
+ throw Throwables.propagate(ex.getCause());
|
|
+ } catch (Exception ex) {
|
|
+ Bukkit.getLogger().log(Level.SEVERE, "Error invoking " + method, ex);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ try {
|
|
+ config.save(CONFIG_FILE);
|
|
+ } catch (IOException ex) {
|
|
+ Bukkit.getLogger().log(Level.SEVERE, "Could not save " + CONFIG_FILE, ex);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private static void set(String path, Object val) {
|
|
+ config.set(path, val);
|
|
+ }
|
|
+
|
|
+ private static boolean getBoolean(String path, boolean def) {
|
|
+ config.addDefault(path, def);
|
|
+ return config.getBoolean(path, config.getBoolean(path));
|
|
+ }
|
|
+
|
|
+ private static double getDouble(String path, double def) {
|
|
+ config.addDefault(path, def);
|
|
+ return config.getDouble(path, config.getDouble(path));
|
|
+ }
|
|
+
|
|
+ private static int getInt(String path, int def) {
|
|
+ config.addDefault(path, def);
|
|
+ return config.getInt(path, config.getInt(path));
|
|
+ }
|
|
+
|
|
+ private static <T> List getList(String path, T def) {
|
|
+ config.addDefault(path, def);
|
|
+ return config.getList(path, config.getList(path));
|
|
+ }
|
|
+
|
|
+ private static String getString(String path, String def) {
|
|
+ config.addDefault(path, def);
|
|
+ return config.getString(path, config.getString(path));
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
|
|
new file mode 100644
|
|
index 0000000000..30f11fe261
|
|
--- /dev/null
|
|
+++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
|
|
@@ -0,0 +1,63 @@
|
|
+package net.pl3x.purpur;
|
|
+
|
|
+import com.destroystokyo.paper.PaperWorldConfig;
|
|
+import org.bukkit.configuration.file.YamlConfiguration;
|
|
+import org.spigotmc.SpigotWorldConfig;
|
|
+
|
|
+import java.util.List;
|
|
+
|
|
+import static net.pl3x.purpur.PurpurConfig.log;
|
|
+
|
|
+public class PurpurWorldConfig {
|
|
+
|
|
+ private final String worldName;
|
|
+ private final PaperWorldConfig paperConfig;
|
|
+ private final SpigotWorldConfig spigotConfig;
|
|
+ private final YamlConfiguration config;
|
|
+ private boolean verbose;
|
|
+
|
|
+ public PurpurWorldConfig(String worldName, PaperWorldConfig paperConfig, SpigotWorldConfig spigotConfig) {
|
|
+ this.worldName = worldName;
|
|
+ this.paperConfig = paperConfig;
|
|
+ this.spigotConfig = spigotConfig;
|
|
+ this.config = PurpurConfig.config;
|
|
+ init();
|
|
+ }
|
|
+
|
|
+ public void init() {
|
|
+ log("-------- World Settings For [" + worldName + "] --------");
|
|
+ PurpurConfig.readConfig(PurpurWorldConfig.class, this);
|
|
+ }
|
|
+
|
|
+ private void set(String path, Object val) {
|
|
+ config.set("world-settings.default." + path, val);
|
|
+ if (config.get("world-settings." + worldName + "." + path) != null) {
|
|
+ config.set("world-settings." + worldName + "." + path, val);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private boolean getBoolean(String path, boolean def) {
|
|
+ config.addDefault("world-settings.default." + path, def);
|
|
+ return config.getBoolean("world-settings." + worldName + "." + path, config.getBoolean("world-settings.default." + path));
|
|
+ }
|
|
+
|
|
+ private double getDouble(String path, double def) {
|
|
+ config.addDefault("world-settings.default." + path, def);
|
|
+ return config.getDouble("world-settings." + worldName + "." + path, config.getDouble("world-settings.default." + path));
|
|
+ }
|
|
+
|
|
+ private int getInt(String path, int def) {
|
|
+ config.addDefault("world-settings.default." + path, def);
|
|
+ return config.getInt("world-settings." + worldName + "." + path, config.getInt("world-settings.default." + path));
|
|
+ }
|
|
+
|
|
+ private <T> List getList(String path, T def) {
|
|
+ config.addDefault("world-settings.default." + path, def);
|
|
+ return config.getList("world-settings." + worldName + "." + path, config.getList("world-settings.default." + path));
|
|
+ }
|
|
+
|
|
+ private String getString(String path, String def) {
|
|
+ config.addDefault("world-settings.default." + path, def);
|
|
+ return config.getString("world-settings." + worldName + "." + path, config.getString("world-settings.default." + path));
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
|
index 8f4ca4c285..6d453b6711 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
|
@@ -794,6 +794,7 @@ public final class CraftServer implements Server {
|
|
|
|
org.spigotmc.SpigotConfig.init((File) console.options.valueOf("spigot-settings")); // Spigot
|
|
com.destroystokyo.paper.PaperConfig.init((File) console.options.valueOf("paper-settings")); // Paper
|
|
+ net.pl3x.purpur.PurpurConfig.init((File) console.options.valueOf("purpur-settings")); // Purpur
|
|
for (WorldServer world : console.getWorlds()) {
|
|
world.worldData.setDifficulty(config.difficulty);
|
|
world.setSpawnFlags(config.spawnMonsters, config.spawnAnimals);
|
|
@@ -810,6 +811,7 @@ public final class CraftServer implements Server {
|
|
}
|
|
world.spigotConfig.init(); // Spigot
|
|
world.paperConfig.init(); // Paper
|
|
+ world.purpurConfig.init(); // Purpur
|
|
}
|
|
|
|
Plugin[] pluginClone = pluginManager.getPlugins().clone(); // Paper
|
|
@@ -828,6 +830,7 @@ public final class CraftServer implements Server {
|
|
reloadData();
|
|
org.spigotmc.SpigotConfig.registerCommands(); // Spigot
|
|
com.destroystokyo.paper.PaperConfig.registerCommands(); // Paper
|
|
+ net.pl3x.purpur.PurpurConfig.registerCommands(); // Purpur
|
|
overrideAllCommandBlockCommands = commandsConfiguration.getStringList("command-block-overrides").contains("*");
|
|
ignoreVanillaPermissions = commandsConfiguration.getBoolean("ignore-vanilla-permissions");
|
|
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java
|
|
index 140b82275c..3a4881fb18 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/Main.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/Main.java
|
|
@@ -137,6 +137,14 @@ public class Main {
|
|
.describedAs("Yml file");
|
|
// Paper end
|
|
|
|
+ // Purpur Start
|
|
+ acceptsAll(asList("purpur", "purpur-settings"), "File for purpur settings")
|
|
+ .withRequiredArg()
|
|
+ .ofType(File.class)
|
|
+ .defaultsTo(new File("purpur.yml"))
|
|
+ .describedAs("Yml file");
|
|
+ // Purpur end
|
|
+
|
|
// Paper start
|
|
acceptsAll(asList("server-name"), "Name of the server")
|
|
.withRequiredArg()
|
|
--
|
|
2.24.0.rc1
|
|
|