reformat spigot package

This commit is contained in:
Lulu13022002 2024-12-16 19:22:36 +01:00
parent 7d29c678f7
commit 57c683647f
No known key found for this signature in database
GPG key ID: 491C8F0B8ACDEB01
11 changed files with 559 additions and 1376 deletions

View file

@ -2,16 +2,13 @@ package org.spigotmc;
import net.minecraft.server.MinecraftServer;
public class AsyncCatcher
{
public class AsyncCatcher {
public static boolean enabled = true;
public static void catchOp(String reason)
{
if ( AsyncCatcher.enabled && Thread.currentThread() != MinecraftServer.getServer().serverThread )
{
MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); // Paper
public static void catchOp(String reason) {
if (AsyncCatcher.enabled && Thread.currentThread() != MinecraftServer.getServer().serverThread) {
MinecraftServer.LOGGER.error("Thread {} failed main thread check: {}", Thread.currentThread().getName(), reason, new Throwable()); // Paper
throw new IllegalStateException("Asynchronous " + reason + "!");
}
}

View file

@ -5,34 +5,29 @@ import java.io.IOException;
import java.io.InputStream;
import net.minecraft.nbt.NbtAccounter;
public class LimitStream extends FilterInputStream
{
public class LimitStream extends FilterInputStream {
private final NbtAccounter limit;
public LimitStream(InputStream is, NbtAccounter limit)
{
public LimitStream(InputStream is, NbtAccounter limit) {
super(is);
this.limit = limit;
}
@Override
public int read() throws IOException
{
public int read() throws IOException {
this.limit.accountBytes(1);
return super.read();
}
@Override
public int read(byte[] b) throws IOException
{
public int read(byte[] b) throws IOException {
this.limit.accountBytes(b.length);
return super.read(b);
}
@Override
public int read(byte[] b, int off, int len) throws IOException
{
public int read(byte[] b, int off, int len) throws IOException {
this.limit.accountBytes(len);
return super.read(b, off, len);
}

View file

@ -1,641 +0,0 @@
/*
* Copyright 2011-2013 Tyler Blair. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and contributors and should not be interpreted as representing official policies,
* either expressed or implied, of anybody else.
*/
package org.spigotmc;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.Proxy;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import net.minecraft.server.MinecraftServer;
import org.bukkit.Bukkit;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration;
/**
* <p> The metrics class obtains data about a plugin and submits statistics about it to the metrics backend. </p> <p>
* Public methods provided by this class: </p>
* <code>
* Graph createGraph(String name); <br/>
* void addCustomData(BukkitMetrics.Plotter plotter); <br/>
* void start(); <br/>
* </code>
*/
public class Metrics {
/**
* The current revision number
*/
private static final int REVISION = 6;
/**
* The base url of the metrics domain
*/
private static final String BASE_URL = "https://mcstats.spigotmc.org";
/**
* The url used to report a server's status
*/
private static final String REPORT_URL = "/report/%s";
/**
* The separator to use for custom data. This MUST NOT change unless you are hosting your own version of metrics and
* want to change it.
*/
private static final String CUSTOM_DATA_SEPARATOR = "~~";
/**
* Interval of time to ping (in minutes)
*/
private static final int PING_INTERVAL = 10;
/**
* All of the custom graphs to submit to metrics
*/
private final Set<Graph> graphs = Collections.synchronizedSet(new HashSet<Graph>());
/**
* The default graph, used for addCustomData when you don't want a specific graph
*/
private final Graph defaultGraph = new Graph("Default");
/**
* The plugin configuration file
*/
private final YamlConfiguration configuration;
/**
* The plugin configuration file
*/
private final File configurationFile;
/**
* Unique server id
*/
private final String guid;
/**
* Debug mode
*/
private final boolean debug;
/**
* Lock for synchronization
*/
private final Object optOutLock = new Object();
/**
* The scheduled task
*/
private volatile Timer task = null;
public Metrics() throws IOException {
// load the config
this.configurationFile = this.getConfigFile();
this.configuration = YamlConfiguration.loadConfiguration(this.configurationFile);
// add some defaults
this.configuration.addDefault("opt-out", false);
this.configuration.addDefault("guid", UUID.randomUUID().toString());
this.configuration.addDefault("debug", false);
// Do we need to create the file?
if (this.configuration.get("guid", null) == null) {
this.configuration.options().header("http://mcstats.org").copyDefaults(true);
this.configuration.save(this.configurationFile);
}
// Load the guid then
this.guid = this.configuration.getString("guid");
this.debug = this.configuration.getBoolean("debug", false);
}
/**
* Construct and create a Graph that can be used to separate specific plotters to their own graphs on the metrics
* website. Plotters can be added to the graph object returned.
*
* @param name The name of the graph
* @return Graph object created. Will never return NULL under normal circumstances unless bad parameters are given
*/
public Graph createGraph(final String name) {
if (name == null) {
throw new IllegalArgumentException("Graph name cannot be null");
}
// Construct the graph object
final Graph graph = new Graph(name);
// Now we can add our graph
this.graphs.add(graph);
// and return back
return graph;
}
/**
* Add a Graph object to BukkitMetrics that represents data for the plugin that should be sent to the backend
*
* @param graph The name of the graph
*/
public void addGraph(final Graph graph) {
if (graph == null) {
throw new IllegalArgumentException("Graph cannot be null");
}
this.graphs.add(graph);
}
/**
* Adds a custom data plotter to the default graph
*
* @param plotter The plotter to use to plot custom data
*/
public void addCustomData(final Plotter plotter) {
if (plotter == null) {
throw new IllegalArgumentException("Plotter cannot be null");
}
// Add the plotter to the graph o/
this.defaultGraph.addPlotter(plotter);
// Ensure the default graph is included in the submitted graphs
this.graphs.add(this.defaultGraph);
}
/**
* Start measuring statistics. This will immediately create an async repeating task as the plugin and send the
* initial data to the metrics backend, and then after that it will post in increments of PING_INTERVAL * 1200
* ticks.
*
* @return True if statistics measuring is running, otherwise false.
*/
public boolean start() {
synchronized (this.optOutLock) {
// Did we opt out?
if (this.isOptOut()) {
return false;
}
// Is metrics already running?
if (this.task != null) {
return true;
}
// Begin hitting the server with glorious data
this.task = new Timer("Spigot Metrics Thread", true);
this.task.scheduleAtFixedRate(new TimerTask() {
private boolean firstPost = true;
public void run() {
try {
// This has to be synchronized or it can collide with the disable method.
synchronized (Metrics.this.optOutLock) {
// Disable Task, if it is running and the server owner decided to opt-out
if (Metrics.this.isOptOut() && Metrics.this.task != null) {
Metrics.this.task.cancel();
Metrics.this.task = null;
// Tell all plotters to stop gathering information.
for (Graph graph : Metrics.this.graphs) {
graph.onOptOut();
}
}
}
// We use the inverse of firstPost because if it is the first time we are posting,
// it is not a interval ping, so it evaluates to FALSE
// Each time thereafter it will evaluate to TRUE, i.e PING!
Metrics.this.postPlugin(!this.firstPost);
// After the first post we set firstPost to false
// Each post thereafter will be a ping
this.firstPost = false;
} catch (IOException e) {
if (Metrics.this.debug) {
Bukkit.getLogger().log(Level.INFO, "[Metrics] " + e.getMessage());
}
}
}
}, 0, TimeUnit.MINUTES.toMillis(Metrics.PING_INTERVAL));
return true;
}
}
/**
* Has the server owner denied plugin metrics?
*
* @return true if metrics should be opted out of it
*/
public boolean isOptOut() {
synchronized (this.optOutLock) {
try {
// Reload the metrics file
this.configuration.load(this.getConfigFile());
} catch (IOException ex) {
if (this.debug) {
Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage());
}
return true;
} catch (InvalidConfigurationException ex) {
if (this.debug) {
Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage());
}
return true;
}
return this.configuration.getBoolean("opt-out", false);
}
}
/**
* Enables metrics for the server by setting "opt-out" to false in the config file and starting the metrics task.
*
* @throws java.io.IOException
*/
public void enable() throws IOException {
// This has to be synchronized or it can collide with the check in the task.
synchronized (this.optOutLock) {
// Check if the server owner has already set opt-out, if not, set it.
if (this.isOptOut()) {
this.configuration.set("opt-out", false);
this.configuration.save(this.configurationFile);
}
// Enable Task, if it is not running
if (this.task == null) {
this.start();
}
}
}
/**
* Disables metrics for the server by setting "opt-out" to true in the config file and canceling the metrics task.
*
* @throws java.io.IOException
*/
public void disable() throws IOException {
// This has to be synchronized or it can collide with the check in the task.
synchronized (this.optOutLock) {
// Check if the server owner has already set opt-out, if not, set it.
if (!this.isOptOut()) {
this.configuration.set("opt-out", true);
this.configuration.save(this.configurationFile);
}
// Disable Task, if it is running
if (this.task != null) {
this.task.cancel();
this.task = null;
}
}
}
/**
* Gets the File object of the config file that should be used to store data such as the GUID and opt-out status
*
* @return the File object for the config file
*/
public File getConfigFile() {
// I believe the easiest way to get the base folder (e.g craftbukkit set via -P) for plugins to use
// is to abuse the plugin object we already have
// plugin.getDataFolder() => base/plugins/PluginA/
// pluginsFolder => base/plugins/
// The base is not necessarily relative to the startup directory.
// File pluginsFolder = plugin.getDataFolder().getParentFile();
// return => base/plugins/PluginMetrics/config.yml
return new File(new File((File) MinecraftServer.getServer().options.valueOf("plugins"), "PluginMetrics"), "config.yml");
}
/**
* Generic method that posts a plugin to the metrics website
*/
private void postPlugin(final boolean isPing) throws IOException {
// Server software specific section
String pluginName = "Spigot";
boolean onlineMode = Bukkit.getServer().getOnlineMode(); // TRUE if online mode is enabled
String pluginVersion = (Metrics.class.getPackage().getImplementationVersion() != null) ? Metrics.class.getPackage().getImplementationVersion() : "unknown";
String serverVersion = Bukkit.getVersion();
int playersOnline = Bukkit.getServer().getOnlinePlayers().size();
// END server software specific section -- all code below does not use any code outside of this class / Java
// Construct the post data
final StringBuilder data = new StringBuilder();
// The plugin's description file containg all of the plugin data such as name, version, author, etc
data.append(Metrics.encode("guid")).append('=').append(Metrics.encode(this.guid));
Metrics.encodeDataPair(data, "version", pluginVersion);
Metrics.encodeDataPair(data, "server", serverVersion);
Metrics.encodeDataPair(data, "players", Integer.toString(playersOnline));
Metrics.encodeDataPair(data, "revision", String.valueOf(Metrics.REVISION));
// New data as of R6
String osname = System.getProperty("os.name");
String osarch = System.getProperty("os.arch");
String osversion = System.getProperty("os.version");
String java_version = System.getProperty("java.version");
int coreCount = Runtime.getRuntime().availableProcessors();
// normalize os arch .. amd64 -> x86_64
if (osarch.equals("amd64")) {
osarch = "x86_64";
}
Metrics.encodeDataPair(data, "osname", osname);
Metrics.encodeDataPair(data, "osarch", osarch);
Metrics.encodeDataPair(data, "osversion", osversion);
Metrics.encodeDataPair(data, "cores", Integer.toString(coreCount));
Metrics.encodeDataPair(data, "online-mode", Boolean.toString(onlineMode));
Metrics.encodeDataPair(data, "java_version", java_version);
// If we're pinging, append it
if (isPing) {
Metrics.encodeDataPair(data, "ping", "true");
}
// Acquire a lock on the graphs, which lets us make the assumption we also lock everything
// inside of the graph (e.g plotters)
synchronized (this.graphs) {
final Iterator<Graph> iter = this.graphs.iterator();
while (iter.hasNext()) {
final Graph graph = iter.next();
for (Plotter plotter : graph.getPlotters()) {
// The key name to send to the metrics server
// The format is C-GRAPHNAME-PLOTTERNAME where separator - is defined at the top
// Legacy (R4) submitters use the format Custom%s, or CustomPLOTTERNAME
final String key = String.format("C%s%s%s%s", Metrics.CUSTOM_DATA_SEPARATOR, graph.getName(), Metrics.CUSTOM_DATA_SEPARATOR, plotter.getColumnName());
// The value to send, which for the foreseeable future is just the string
// value of plotter.getValue()
final String value = Integer.toString(plotter.getValue());
// Add it to the http post data :)
Metrics.encodeDataPair(data, key, value);
}
}
}
// Create the url
URL url = new URL(Metrics.BASE_URL + String.format(Metrics.REPORT_URL, Metrics.encode(pluginName)));
// Connect to the website
URLConnection connection;
// Mineshafter creates a socks proxy, so we can safely bypass it
// It does not reroute POST requests so we need to go around it
if (this.isMineshafterPresent()) {
connection = url.openConnection(Proxy.NO_PROXY);
} else {
connection = url.openConnection();
}
connection.setDoOutput(true);
// Write the data
final OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream());
writer.write(data.toString());
writer.flush();
// Now read the response
final BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
final String response = reader.readLine();
// close resources
writer.close();
reader.close();
if (response == null || response.startsWith("ERR")) {
throw new IOException(response); //Throw the exception
} else {
// Is this the first update this hour?
if (response.contains("OK This is your first update this hour")) {
synchronized (this.graphs) {
final Iterator<Graph> iter = this.graphs.iterator();
while (iter.hasNext()) {
final Graph graph = iter.next();
for (Plotter plotter : graph.getPlotters()) {
plotter.reset();
}
}
}
}
}
}
/**
* Check if mineshafter is present. If it is, we need to bypass it to send POST requests
*
* @return true if mineshafter is installed on the server
*/
private boolean isMineshafterPresent() {
try {
Class.forName("mineshafter.MineServer");
return true;
} catch (Exception e) {
return false;
}
}
/**
* <p>Encode a key/value data pair to be used in a HTTP post request. This INCLUDES a & so the first key/value pair
* MUST be included manually, e.g:</p>
* <code>
* StringBuffer data = new StringBuffer();
* data.append(encode("guid")).append('=').append(encode(guid));
* encodeDataPair(data, "version", description.getVersion());
* </code>
*
* @param buffer the stringbuilder to append the data pair onto
* @param key the key value
* @param value the value
*/
private static void encodeDataPair(final StringBuilder buffer, final String key, final String value) throws UnsupportedEncodingException {
buffer.append('&').append(Metrics.encode(key)).append('=').append(Metrics.encode(value));
}
/**
* Encode text as UTF-8
*
* @param text the text to encode
* @return the encoded text, as UTF-8
*/
private static String encode(final String text) throws UnsupportedEncodingException {
return URLEncoder.encode(text, "UTF-8");
}
/**
* Represents a custom graph on the website
*/
public static class Graph {
/**
* The graph's name, alphanumeric and spaces only :) If it does not comply to the above when submitted, it is
* rejected
*/
private final String name;
/**
* The set of plotters that are contained within this graph
*/
private final Set<Plotter> plotters = new LinkedHashSet<Plotter>();
private Graph(final String name) {
this.name = name;
}
/**
* Gets the graph's name
*
* @return the Graph's name
*/
public String getName() {
return this.name;
}
/**
* Add a plotter to the graph, which will be used to plot entries
*
* @param plotter the plotter to add to the graph
*/
public void addPlotter(final Plotter plotter) {
this.plotters.add(plotter);
}
/**
* Remove a plotter from the graph
*
* @param plotter the plotter to remove from the graph
*/
public void removePlotter(final Plotter plotter) {
this.plotters.remove(plotter);
}
/**
* Gets an <b>unmodifiable</b> set of the plotter objects in the graph
*
* @return an unmodifiable {@link java.util.Set} of the plotter objects
*/
public Set<Plotter> getPlotters() {
return Collections.unmodifiableSet(this.plotters);
}
@Override
public int hashCode() {
return this.name.hashCode();
}
@Override
public boolean equals(final Object object) {
if (!(object instanceof Graph)) {
return false;
}
final Graph graph = (Graph) object;
return graph.name.equals(this.name);
}
/**
* Called when the server owner decides to opt-out of BukkitMetrics while the server is running.
*/
protected void onOptOut() {
}
}
/**
* Interface used to collect custom data for a plugin
*/
public abstract static class Plotter {
/**
* The plot's name
*/
private final String name;
/**
* Construct a plotter with the default plot name
*/
public Plotter() {
this("Default");
}
/**
* Construct a plotter with a specific plot name
*
* @param name the name of the plotter to use, which will show up on the website
*/
public Plotter(final String name) {
this.name = name;
}
/**
* Get the current value for the plotted point. Since this function defers to an external function it may or may
* not return immediately thus cannot be guaranteed to be thread friendly or safe. This function can be called
* from any thread so care should be taken when accessing resources that need to be synchronized.
*
* @return the current value for the point to be plotted.
*/
public abstract int getValue();
/**
* Get the column name for the plotted point
*
* @return the plotted point's column name
*/
public String getColumnName() {
return this.name;
}
/**
* Called after the website graphs have been updated
*/
public void reset() {
}
@Override
public int hashCode() {
return this.getColumnName().hashCode();
}
@Override
public boolean equals(final Object object) {
if (!(object instanceof Plotter)) {
return false;
}
final Plotter plotter = (Plotter) object;
return plotter.name.equals(this.name) && plotter.getValue() == this.getValue();
}
}
}

View file

@ -1,18 +1,16 @@
package org.spigotmc;
import java.io.File;
import java.util.List;
import java.util.Locale;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.craftbukkit.util.CraftChatMessage;
public class RestartCommand extends Command
{
public class RestartCommand extends Command {
public RestartCommand(String name)
{
public RestartCommand(String name) {
super(name);
this.description = "Restarts the server";
this.usageMessage = "/restart";
@ -20,39 +18,25 @@ public class RestartCommand extends Command
}
@Override
public boolean execute(CommandSender sender, String currentAlias, String[] args)
{
if ( this.testPermission( sender ) )
{
MinecraftServer.getServer().processQueue.add( new Runnable()
{
@Override
public void run()
{
RestartCommand.restart();
}
} );
public boolean execute(CommandSender sender, String currentAlias, String[] args) {
if (this.testPermission(sender)) {
MinecraftServer.getServer().processQueue.add(RestartCommand::restart);
}
return true;
}
public static void restart()
{
public static void restart() {
RestartCommand.restart(SpigotConfig.restartScript);
}
private static void restart(final String restartScript)
{
private static void restart(final String restartScript) {
AsyncCatcher.enabled = false; // Disable async catcher in case it interferes with us
try
{
try {
// Paper - extract method and cleanup
boolean isRestarting = addShutdownHook(restartScript);
if ( isRestarting )
{
if (isRestarting) {
System.out.println("Attempting to restart with " + SpigotConfig.restartScript);
} else
{
} else {
System.out.println("Startup script '" + SpigotConfig.restartScript + "' does not exist! Stopping server.");
}
// Stop the watchdog
@ -60,56 +44,42 @@ public class RestartCommand extends Command
shutdownServer(isRestarting);
// Paper end
} catch ( Exception ex )
{
} catch (Exception ex) {
ex.printStackTrace();
}
}
// Paper start - sync copied from above with minor changes, async added
private static void shutdownServer(boolean isRestarting)
{
if ( MinecraftServer.getServer().isSameThread() )
{
private static void shutdownServer(boolean isRestarting) {
if (MinecraftServer.getServer().isSameThread()) {
// Kick all players
for ( ServerPlayer p : com.google.common.collect.ImmutableList.copyOf( MinecraftServer.getServer().getPlayerList().players ) )
{
p.connection.disconnect( CraftChatMessage.fromStringOrEmpty( SpigotConfig.restartMessage, true ), org.bukkit.event.player.PlayerKickEvent.Cause.RESTART_COMMAND); // Paper - kick event reason (cause is never used))
for (ServerPlayer p : com.google.common.collect.ImmutableList.copyOf(MinecraftServer.getServer().getPlayerList().players)) {
p.connection.disconnect(CraftChatMessage.fromStringOrEmpty(SpigotConfig.restartMessage, true), org.bukkit.event.player.PlayerKickEvent.Cause.RESTART_COMMAND); // Paper - kick event reason (cause is never used)
}
// Give the socket a chance to send the packets
try
{
try {
Thread.sleep(100);
} catch ( InterruptedException ex )
{
} catch (InterruptedException ex) {
}
closeSocket();
// Actually shutdown
try
{
try {
MinecraftServer.getServer().close(); // calls stop()
} catch ( Throwable t )
{
} catch (Throwable t) {
}
// Actually stop the JVM
System.exit(0);
} else
{
} else {
// Mark the server to shutdown at the end of the tick
MinecraftServer.getServer().safeShutdown(false, isRestarting);
// wait 10 seconds to see if we're actually going to try shutdown
try
{
try {
Thread.sleep(10000);
}
catch (InterruptedException ignored)
{
}
} catch (InterruptedException ignored) {}
// Check if we've actually hit a state where the server is going to safely shutdown
// if we have, let the server stop as usual
@ -123,57 +93,40 @@ public class RestartCommand extends Command
// Paper end
// Paper - Split from moved code
private static void closeSocket()
{
private static void closeSocket() {
// Close the socket so we can rebind with the new process
MinecraftServer.getServer().getConnection().stop();
// Give time for it to kick in
try
{
try {
Thread.sleep(100);
} catch ( InterruptedException ex )
{
}
} catch (InterruptedException ignored) {}
}
// Paper end
// Paper start - copied from above and modified to return if the hook registered
private static boolean addShutdownHook(String restartScript)
{
private static boolean addShutdownHook(String restartScript) {
String[] split = restartScript.split(" ");
if ( split.length > 0 && new File( split[0] ).isFile() )
{
Thread shutdownHook = new Thread()
{
@Override
public void run()
{
try
{
String os = System.getProperty( "os.name" ).toLowerCase(java.util.Locale.ENGLISH);
if ( os.contains( "win" ) )
{
if (split.length > 0 && new File(split[0]).isFile()) {
Thread shutdownHook = new Thread(() -> {
try {
String os = System.getProperty("os.name").toLowerCase(Locale.ENGLISH);
if (os.contains("win")) {
Runtime.getRuntime().exec("cmd /c start " + restartScript);
} else
{
} else {
Runtime.getRuntime().exec("sh " + restartScript);
}
} catch ( Exception e )
{
} catch (Exception e) {
e.printStackTrace();
}
}
};
});
shutdownHook.setDaemon(true);
Runtime.getRuntime().addShutdownHook(shutdownHook);
return true;
} else
{
} else {
return false;
}
}
// Paper end
}

View file

@ -1,12 +1,14 @@
package org.spigotmc;
import java.io.File;
import net.kyori.adventure.text.format.NamedTextColor;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import static net.kyori.adventure.text.Component.text;
public class SpigotCommand extends Command {
public SpigotCommand(String name) {
@ -21,13 +23,17 @@ public class SpigotCommand extends Command {
if (!this.testPermission(sender)) return true;
if (args.length != 1) {
sender.sendMessage(ChatColor.RED + "Usage: " + this.usageMessage);
sender.sendMessage(text("Usage: " + this.usageMessage, NamedTextColor.RED));
return false;
}
if (args[0].equals("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.");
Command.broadcastCommandMessage(sender, text().color(NamedTextColor.RED)
.append(text("Please note that this command is not supported and may cause issues."))
.appendNewline()
.append(text("If you encounter any issues please use the /stop command to restart your server."))
.build()
);
MinecraftServer console = MinecraftServer.getServer();
org.spigotmc.SpigotConfig.init((File) console.options.valueOf("spigot-settings"));
@ -36,7 +42,7 @@ public class SpigotCommand extends Command {
}
console.server.reloadCount++;
Command.broadcastCommandMessage(sender, ChatColor.GREEN + "Reload complete.");
Command.broadcastCommandMessage(sender, text("Reload complete.", NamedTextColor.GREEN));
}
return true;

View file

@ -28,39 +28,35 @@ import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration;
public class SpigotConfig
{
public class SpigotConfig {
private static File CONFIG_FILE;
private static final String HEADER = "This is the main configuration file for Spigot.\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"
+ "For a reference for any variable inside this file, check out the Spigot wiki at\n"
+ "http://www.spigotmc.org/wiki/spigot-configuration/\n"
+ "\n"
+ "If you need help with the configuration or have any questions related to Spigot,\n"
+ "join us at the Discord or drop by our forums and leave a post.\n"
+ "\n"
+ "Discord: https://www.spigotmc.org/go/discord\n"
+ "Forums: http://www.spigotmc.org/\n";
private static final String HEADER = """
This is the main configuration file for Spigot.
As you can see, there's tons to configure. Some options may impact gameplay, so use
with caution, and make sure you know what each option does before configuring.
For a reference for any variable inside this file, check out the Spigot wiki at
http://www.spigotmc.org/wiki/spigot-configuration/
If you need help with the configuration or have any questions related to Spigot,
join us at the Discord or drop by our forums and leave a post.
Discord: https://www.spigotmc.org/go/discord
Forums: http://www.spigotmc.org/
""";
/*========================================================================*/
public static YamlConfiguration config;
static int version;
static Map<String, Command> commands;
/*========================================================================*/
private static Metrics metrics;
public static void init(File configFile)
{
public static void init(File configFile) {
SpigotConfig.CONFIG_FILE = configFile;
SpigotConfig.config = new YamlConfiguration();
try
{
try {
SpigotConfig.config.load(SpigotConfig.CONFIG_FILE);
} catch ( IOException ex )
{
} catch ( InvalidConfigurationException ex )
{
} catch (IOException ignored) {
} catch (InvalidConfigurationException ex) {
Bukkit.getLogger().log(Level.SEVERE, "Could not load spigot.yml, please correct your syntax errors", ex);
throw Throwables.propagate(ex);
}
@ -68,7 +64,7 @@ public class SpigotConfig
SpigotConfig.config.options().header(SpigotConfig.HEADER);
SpigotConfig.config.options().copyDefaults(true);
SpigotConfig.commands = new HashMap<String, Command>();
SpigotConfig.commands = new HashMap<>();
SpigotConfig.commands.put("spigot", new SpigotCommand("spigot"));
SpigotConfig.version = SpigotConfig.getInt("config-version", 12);
@ -76,113 +72,77 @@ public class SpigotConfig
SpigotConfig.readConfig(SpigotConfig.class, null);
}
public static void registerCommands()
{
for ( Map.Entry<String, Command> entry : SpigotConfig.commands.entrySet() )
{
public static void registerCommands() {
for (Map.Entry<String, Command> entry : SpigotConfig.commands.entrySet()) {
MinecraftServer.getServer().server.getCommandMap().register(entry.getKey(), "Spigot", entry.getValue());
}
/* // Paper - Replace with our own
if ( SpigotConfig.metrics == null )
{
try
{
SpigotConfig.metrics = new Metrics();
SpigotConfig.metrics.start();
} catch ( IOException ex )
{
Bukkit.getServer().getLogger().log( Level.SEVERE, "Could not start metrics service", ex );
}
}
*/ // Paper end
}
public static void readConfig(Class<?> clazz, Object instance) // Paper - package-private -> public
{
for ( Method method : clazz.getDeclaredMethods() )
{
if ( Modifier.isPrivate( method.getModifiers() ) )
{
if ( method.getParameterTypes().length == 0 && method.getReturnType() == Void.TYPE )
{
try
{
public static void readConfig(Class<?> clazz, Object instance) { // Paper - package-private -> public
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 )
{
} catch (InvocationTargetException ex) {
throw Throwables.propagate(ex.getCause());
} catch ( Exception ex )
{
} catch (Exception ex) {
Bukkit.getLogger().log(Level.SEVERE, "Error invoking " + method, ex);
}
}
}
}
try
{
try {
SpigotConfig.config.save(SpigotConfig.CONFIG_FILE);
} catch ( IOException ex )
{
} catch (IOException ex) {
Bukkit.getLogger().log(Level.SEVERE, "Could not save " + SpigotConfig.CONFIG_FILE, ex);
}
}
private static void set(String path, Object val)
{
private static void set(String path, Object val) {
SpigotConfig.config.set(path, val);
}
private static boolean getBoolean(String path, boolean def)
{
private static boolean getBoolean(String path, boolean def) {
SpigotConfig.config.addDefault(path, def);
return SpigotConfig.config.getBoolean(path, SpigotConfig.config.getBoolean(path));
}
private static int getInt(String path, int def)
{
private static int getInt(String path, int def) {
SpigotConfig.config.addDefault(path, def);
return SpigotConfig.config.getInt(path, SpigotConfig.config.getInt(path));
}
private static <T> List getList(String path, T def)
{
private static <T> List getList(String path, T def) {
SpigotConfig.config.addDefault(path, def);
return (List<T>) SpigotConfig.config.getList(path, SpigotConfig.config.getList(path));
}
private static String getString(String path, String def)
{
private static String getString(String path, String def) {
SpigotConfig.config.addDefault(path, def);
return SpigotConfig.config.getString(path, SpigotConfig.config.getString(path));
}
private static double getDouble(String path, double def)
{
private static double getDouble(String path, double def) {
SpigotConfig.config.addDefault(path, def);
return SpigotConfig.config.getDouble(path, SpigotConfig.config.getDouble(path));
}
public static boolean logCommands;
private static void logCommands()
{
private static void logCommands() {
SpigotConfig.logCommands = SpigotConfig.getBoolean("commands.log", true);
}
public static int tabComplete;
public static boolean sendNamespaced;
private static void tabComplete()
{
if ( SpigotConfig.version < 6 )
{
private static void tabComplete() {
if (SpigotConfig.version < 6) {
boolean oldValue = SpigotConfig.getBoolean("commands.tab-complete", true);
if ( oldValue )
{
if (oldValue) {
SpigotConfig.set("commands.tab-complete", 0);
} else
{
} else {
SpigotConfig.set("commands.tab-complete", -1);
}
}
@ -194,15 +154,14 @@ public class SpigotConfig
public static String unknownCommandMessage;
public static String serverFullMessage;
public static String outdatedClientMessage = "Outdated client! Please use {0}";
public static String outdatedServerMessage = "Outdated server! I\'m still on {0}";
private static String transform(String s)
{
public static String outdatedServerMessage = "Outdated server! I'm still on {0}";
private static String transform(String s) {
return ChatColor.translateAlternateColorCodes('&', s).replaceAll("\\\\n", "\n");
}
private static void messages()
{
if (SpigotConfig.version < 8)
{
private static void messages() {
if (SpigotConfig.version < 8) {
SpigotConfig.set("messages.outdated-client", SpigotConfig.outdatedClientMessage);
SpigotConfig.set("messages.outdated-server", SpigotConfig.outdatedServerMessage);
}
@ -218,28 +177,24 @@ public class SpigotConfig
public static boolean restartOnCrash = true;
public static String restartScript = "./start.sh";
public static String restartMessage;
private static void watchdog()
{
private static void watchdog() {
SpigotConfig.timeoutTime = SpigotConfig.getInt("settings.timeout-time", SpigotConfig.timeoutTime);
SpigotConfig.restartOnCrash = SpigotConfig.getBoolean("settings.restart-on-crash", SpigotConfig.restartOnCrash);
SpigotConfig.restartScript = SpigotConfig.getString("settings.restart-script", SpigotConfig.restartScript);
SpigotConfig.restartMessage = SpigotConfig.transform(SpigotConfig.getString("messages.restart", "Server is restarting"));
SpigotConfig.commands.put("restart", new RestartCommand("restart"));
// WatchdogThread.doStart( SpigotConfig.timeoutTime, SpigotConfig.restartOnCrash ); // Paper - moved to after paper config initialization
}
public static boolean bungee;
private static void bungee() {
if ( SpigotConfig.version < 4 )
{
if (SpigotConfig.version < 4) {
SpigotConfig.set("settings.bungeecord", false);
System.out.println("Outdated config, disabling BungeeCord support!");
}
SpigotConfig.bungee = SpigotConfig.getBoolean("settings.bungeecord", false);
}
private static void nettyThreads()
{
private static void nettyThreads() {
int count = SpigotConfig.getInt("settings.netty-threads", 4);
System.setProperty("io.netty.eventLoopThreads", Integer.toString(count));
Bukkit.getLogger().log(Level.INFO, "Using {0} threads for Netty based IO", count);
@ -247,8 +202,8 @@ public class SpigotConfig
public static boolean disableStatSaving;
public static Map<ResourceLocation, Integer> forcedStats = new HashMap<>();
private static void stats()
{
private static void stats() {
SpigotConfig.disableStatSaving = SpigotConfig.getBoolean("stats.disable-saving", false);
if (!SpigotConfig.config.contains("stats.forced-stats")) {
@ -256,102 +211,82 @@ public class SpigotConfig
}
ConfigurationSection section = SpigotConfig.config.getConfigurationSection("stats.forced-stats");
for ( String name : section.getKeys( true ) )
{
if ( section.isInt( name ) )
{
try
{
for (String name : section.getKeys(true)) {
if (section.isInt(name)) {
try {
ResourceLocation key = ResourceLocation.parse(name);
if ( BuiltInRegistries.CUSTOM_STAT.get( key ) == null )
{
if (BuiltInRegistries.CUSTOM_STAT.get(key) == null) {
Bukkit.getLogger().log(Level.WARNING, "Ignoring non existent stats.forced-stats " + name);
continue;
}
SpigotConfig.forcedStats.put(key, section.getInt(name));
} catch (Exception ex)
{
} catch (Exception ex) {
Bukkit.getLogger().log(Level.WARNING, "Ignoring invalid stats.forced-stats " + name);
}
}
}
}
private static void tpsCommand()
{
private static void tpsCommand() {
SpigotConfig.commands.put("tps", new TicksPerSecondCommand("tps"));
}
public static int playerSample;
private static void playerSample()
{
private static void playerSample() {
SpigotConfig.playerSample = Math.max(SpigotConfig.getInt("settings.sample-count", 12), 0); // Paper - Avoid negative counts
Bukkit.getLogger().log(Level.INFO, "Server Ping Player Sample Count: {0}", playerSample); // Paper - Use logger
}
public static int playerShuffle;
private static void playerShuffle()
{
private static void playerShuffle() {
SpigotConfig.playerShuffle = SpigotConfig.getInt("settings.player-shuffle", 0);
}
public static List<String> spamExclusions;
private static void spamExclusions()
{
SpigotConfig.spamExclusions = SpigotConfig.getList( "commands.spam-exclusions", Arrays.asList( new String[]
{
"/skill"
} ) );
private static void spamExclusions() {
SpigotConfig.spamExclusions = SpigotConfig.getList("commands.spam-exclusions", List.of("/skill"));
}
public static boolean silentCommandBlocks;
private static void silentCommandBlocks()
{
private static void silentCommandBlocks() {
SpigotConfig.silentCommandBlocks = SpigotConfig.getBoolean("commands.silent-commandblock-console", false);
}
public static Set<String> replaceCommands;
private static void replaceCommands()
{
if ( SpigotConfig.config.contains( "replace-commands" ) )
{
private static void replaceCommands() {
if (SpigotConfig.config.contains("replace-commands")) {
SpigotConfig.set("commands.replace-commands", SpigotConfig.config.getStringList("replace-commands"));
SpigotConfig.config.set("replace-commands", null);
}
SpigotConfig.replaceCommands = new HashSet<String>( (List<String>) SpigotConfig.getList( "commands.replace-commands",
SpigotConfig.replaceCommands = new HashSet<>(SpigotConfig.getList("commands.replace-commands",
Arrays.asList("setblock", "summon", "testforblock", "tellraw")));
}
public static int userCacheCap;
private static void userCacheCap()
{
private static void userCacheCap() {
SpigotConfig.userCacheCap = SpigotConfig.getInt("settings.user-cache-size", 1000);
}
public static boolean saveUserCacheOnStopOnly;
private static void saveUserCacheOnStopOnly()
{
private static void saveUserCacheOnStopOnly() {
SpigotConfig.saveUserCacheOnStopOnly = SpigotConfig.getBoolean("settings.save-user-cache-on-stop-only", false);
}
public static double movedWronglyThreshold;
private static void movedWronglyThreshold()
{
private static void movedWronglyThreshold() {
SpigotConfig.movedWronglyThreshold = SpigotConfig.getDouble("settings.moved-wrongly-threshold", 0.0625D);
}
public static double movedTooQuicklyMultiplier;
private static void movedTooQuicklyMultiplier()
{
private static void movedTooQuicklyMultiplier() {
SpigotConfig.movedTooQuicklyMultiplier = SpigotConfig.getDouble("settings.moved-too-quickly-multiplier", 10.0D);
}
public static double maxAbsorption = 2048;
public static double maxHealth = 2048;
public static double movementSpeed = 2048;
public static double maxHealth = 1024;
public static double movementSpeed = 1024;
public static double attackDamage = 2048;
private static void attributeMaxes()
{
private static void attributeMaxes() {
SpigotConfig.maxAbsorption = SpigotConfig.getDouble("settings.attribute.maxAbsorption.max", SpigotConfig.maxAbsorption);
((RangedAttribute) Attributes.MAX_ABSORPTION.value()).maxValue = SpigotConfig.maxAbsorption;
SpigotConfig.maxHealth = SpigotConfig.getDouble("settings.attribute.maxHealth.max", SpigotConfig.maxHealth);
@ -363,12 +298,10 @@ public class SpigotConfig
}
public static boolean debug;
private static void debug()
{
private static void debug() {
SpigotConfig.debug = SpigotConfig.getBoolean("settings.debug", false);
if ( SpigotConfig.debug && !LogManager.getRootLogger().isTraceEnabled() )
{
if (SpigotConfig.debug && !LogManager.getRootLogger().isTraceEnabled()) {
// Enable debug logging
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
Configuration conf = ctx.getConfiguration();
@ -376,12 +309,8 @@ public class SpigotConfig
ctx.updateLoggers(conf);
}
if ( LogManager.getRootLogger().isTraceEnabled() )
{
if (LogManager.getRootLogger().isTraceEnabled()) {
Bukkit.getLogger().info("Debug logging is enabled");
} else
{
// Bukkit.getLogger().info( "Debug logging is disabled" ); // Paper - Don't log if debug logging isn't enabled.
}
}
@ -389,7 +318,7 @@ public class SpigotConfig
public static List<String> disabledAdvancements;
private static void disabledAdvancements() {
SpigotConfig.disableAdvancementSaving = SpigotConfig.getBoolean("advancements.disable-saving", false);
SpigotConfig.disabledAdvancements = SpigotConfig.getList("advancements.disabled", Arrays.asList(new String[]{"minecraft:story/disabled"}));
SpigotConfig.disabledAdvancements = SpigotConfig.getList("advancements.disabled", List.of("minecraft:story/disabled"));
}
public static boolean logVillagerDeaths;

View file

@ -4,84 +4,71 @@ import java.util.List;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.YamlConfiguration;
public class SpigotWorldConfig
{
public class SpigotWorldConfig {
private final String worldName;
private final YamlConfiguration config;
private boolean verbose;
public SpigotWorldConfig(String worldName)
{
public SpigotWorldConfig(String worldName) {
this.worldName = worldName;
this.config = SpigotConfig.config;
this.init();
}
public void init()
{
public void init() {
this.verbose = this.getBoolean("verbose", false); // Paper
this.log("-------- World Settings For [" + this.worldName + "] --------");
SpigotConfig.readConfig(SpigotWorldConfig.class, this);
}
private void log(String s)
{
if ( this.verbose )
{
private void log(String s) {
if (this.verbose) {
Bukkit.getLogger().info(s);
}
}
private void set(String path, Object val)
{
private void set(String path, Object val) {
this.config.set("world-settings.default." + path, val);
}
public boolean getBoolean(String path, boolean def)
{
public boolean getBoolean(String path, boolean def) {
this.config.addDefault("world-settings.default." + path, def);
return this.config.getBoolean("world-settings." + this.worldName + "." + path, this.config.getBoolean("world-settings.default." + path));
}
public double getDouble(String path, double def)
{
public double getDouble(String path, double def) {
this.config.addDefault("world-settings.default." + path, def);
return this.config.getDouble("world-settings." + this.worldName + "." + path, this.config.getDouble("world-settings.default." + path));
}
public int getInt(String path)
{
public int getInt(String path) {
return this.config.getInt("world-settings." + this.worldName + "." + path);
}
public int getInt(String path, int def)
{
public int getInt(String path, int def) {
// Paper start - get int without setting default
return this.getInt(path, def, true);
}
public int getInt(String path, int def, boolean setDef)
{
public int getInt(String path, int def, boolean setDef) {
if (setDef) this.config.addDefault("world-settings.default." + path, def);
return this.config.getInt("world-settings." + this.worldName + "." + path, this.config.getInt("world-settings.default." + path, def));
// Paper end
}
public <T> List getList(String path, T def)
{
public <T> List getList(String path, T def) {
this.config.addDefault("world-settings.default." + path, def);
return (List<T>) this.config.getList("world-settings." + this.worldName + "." + path, this.config.getList("world-settings.default." + path));
}
public String getString(String path, String def)
{
public String getString(String path, String def) {
this.config.addDefault("world-settings.default." + path, def);
return this.config.getString("world-settings." + this.worldName + "." + path, this.config.getString("world-settings.default." + path));
}
private Object get(String path, Object def)
{
private Object get(String path, Object def) {
this.config.addDefault("world-settings.default." + path, def);
return this.config.get("world-settings." + this.worldName + "." + path, this.config.get("world-settings.default." + path));
}
@ -109,11 +96,10 @@ public class SpigotWorldConfig
public int caveVinesModifier;
public int glowBerryModifier; // Paper
public int pitcherPlantModifier; // Paper
private int getAndValidateGrowth(String crop)
{
private int getAndValidateGrowth(String crop) {
int modifier = this.getInt("growth." + crop.toLowerCase(java.util.Locale.ENGLISH) + "-modifier", 100);
if ( modifier == 0 )
{
if (modifier == 0) {
this.log("Cannot set " + crop + " growth to zero, defaulting to 100");
modifier = 100;
}
@ -121,8 +107,8 @@ public class SpigotWorldConfig
return modifier;
}
private void growthModifiers()
{
private void growthModifiers() {
this.cactusModifier = this.getAndValidateGrowth("Cactus");
this.caneModifier = this.getAndValidateGrowth("Cane");
this.melonModifier = this.getAndValidateGrowth("Melon");
@ -148,31 +134,26 @@ public class SpigotWorldConfig
}
public double itemMerge;
private void itemMerge()
{
private void itemMerge() {
this.itemMerge = this.getDouble("merge-radius.item", 0.5);
this.log("Item Merge Radius: " + this.itemMerge);
}
public double expMerge;
private void expMerge()
{
private void expMerge() {
this.expMerge = this.getDouble("merge-radius.exp", -1);
this.log("Experience Merge Radius: " + this.expMerge);
}
public int viewDistance;
private void viewDistance()
{
if ( SpigotConfig.version < 12 )
{
private void viewDistance() {
if (SpigotConfig.version < 12) {
this.set("view-distance", null);
}
Object viewDistanceObject = this.get("view-distance", "default");
this.viewDistance = (viewDistanceObject) instanceof Number ? ((Number) viewDistanceObject).intValue() : -1;
if ( this.viewDistance <= 0 )
{
if (this.viewDistance <= 0) {
this.viewDistance = Bukkit.getViewDistance();
}
@ -181,12 +162,10 @@ public class SpigotWorldConfig
}
public int simulationDistance;
private void simulationDistance()
{
private void simulationDistance() {
Object simulationDistanceObject = this.get("simulation-distance", "default");
this.simulationDistance = (simulationDistanceObject) instanceof Number ? ((Number) simulationDistanceObject).intValue() : -1;
if ( this.simulationDistance <= 0 )
{
if (this.simulationDistance <= 0) {
this.simulationDistance = Bukkit.getSimulationDistance();
}
@ -194,15 +173,13 @@ public class SpigotWorldConfig
}
public byte mobSpawnRange;
private void mobSpawnRange()
{
private void mobSpawnRange() {
this.mobSpawnRange = (byte) getInt("mob-spawn-range", 8); // Paper - Vanilla
this.log("Mob Spawn Range: " + this.mobSpawnRange);
}
public int itemDespawnRate;
private void itemDespawnRate()
{
private void itemDespawnRate() {
this.itemDespawnRate = this.getInt("item-despawn-rate", 6000);
this.log("Item Despawn Rate: " + this.itemDespawnRate);
}
@ -233,8 +210,8 @@ public class SpigotWorldConfig
// Paper end
public boolean tickInactiveVillagers = true;
public boolean ignoreSpectatorActivation = false;
private void activationRange()
{
private void activationRange() {
boolean hasAnimalsConfig = config.getInt("entity-activation-range.animals", this.animalActivationRange) != this.animalActivationRange; // Paper
this.animalActivationRange = this.getInt("entity-activation-range.animals", this.animalActivationRange);
this.monsterActivationRange = this.getInt("entity-activation-range.monsters", this.monsterActivationRange);
@ -276,8 +253,7 @@ public class SpigotWorldConfig
public int miscTrackingRange = 96;
public int displayTrackingRange = 128;
public int otherTrackingRange = 64;
private void trackingRange()
{
private void trackingRange() {
this.playerTrackingRange = this.getInt("entity-tracking-range.players", this.playerTrackingRange);
this.animalTrackingRange = this.getInt("entity-tracking-range.animals", this.animalTrackingRange);
this.monsterTrackingRange = this.getInt("entity-tracking-range.monsters", this.monsterTrackingRange);
@ -291,12 +267,10 @@ public class SpigotWorldConfig
public int hopperCheck;
public int hopperAmount;
public boolean hopperCanLoadChunks;
private void hoppers()
{
private void hoppers() {
// Set the tick delay between hopper item movements
this.hopperTransfer = this.getInt("ticks-per.hopper-transfer", 8);
if ( SpigotConfig.version < 11 )
{
if (SpigotConfig.version < 11) {
this.set("ticks-per.hopper-check", 1);
}
this.hopperCheck = this.getInt("ticks-per.hopper-check", 1);
@ -307,49 +281,42 @@ public class SpigotWorldConfig
public int arrowDespawnRate;
public int tridentDespawnRate;
private void arrowDespawnRate()
{
private void arrowDespawnRate() {
this.arrowDespawnRate = this.getInt("arrow-despawn-rate", 1200);
this.tridentDespawnRate = this.getInt("trident-despawn-rate", this.arrowDespawnRate);
this.log("Arrow Despawn Rate: " + this.arrowDespawnRate + " Trident Respawn Rate:" + this.tridentDespawnRate);
}
public boolean zombieAggressiveTowardsVillager;
private void zombieAggressiveTowardsVillager()
{
private void zombieAggressiveTowardsVillager() {
this.zombieAggressiveTowardsVillager = this.getBoolean("zombie-aggressive-towards-villager", true);
this.log("Zombie Aggressive Towards Villager: " + this.zombieAggressiveTowardsVillager);
}
public boolean nerfSpawnerMobs;
private void nerfSpawnerMobs()
{
private void nerfSpawnerMobs() {
this.nerfSpawnerMobs = this.getBoolean("nerf-spawner-mobs", false);
this.log("Nerfing mobs spawned from spawners: " + this.nerfSpawnerMobs);
}
public boolean enableZombiePigmenPortalSpawns;
private void enableZombiePigmenPortalSpawns()
{
private void enableZombiePigmenPortalSpawns() {
this.enableZombiePigmenPortalSpawns = this.getBoolean("enable-zombie-pigmen-portal-spawns", true);
this.log("Allow Zombie Pigmen to spawn from portal blocks: " + this.enableZombiePigmenPortalSpawns);
}
public int dragonDeathSoundRadius;
private void keepDragonDeathPerWorld()
{
private void keepDragonDeathPerWorld() {
this.dragonDeathSoundRadius = this.getInt("dragon-death-sound-radius", 0);
}
public int witherSpawnSoundRadius;
private void witherSpawnSoundRadius()
{
private void witherSpawnSoundRadius() {
this.witherSpawnSoundRadius = this.getInt("wither-spawn-sound-radius", 0);
}
public int endPortalSoundRadius;
private void endPortalSoundRadius()
{
private void endPortalSoundRadius() {
this.endPortalSoundRadius = this.getInt("end-portal-sound-radius", 0);
}
@ -375,13 +342,14 @@ public class SpigotWorldConfig
public int buriedTreasureSeed;
public Integer mineshaftSeed;
public Long strongholdSeed;
private <N extends Number> N getSeed(String path, java.util.function.Function<String, N> toNumberFunc) {
final String value = this.getString(path, "default");
return org.apache.commons.lang3.math.NumberUtils.isParsable(value) ? toNumberFunc.apply(value) : null;
}
// Paper end
private void initWorldGenSeeds()
{
private void initWorldGenSeeds() {
this.villageSeed = this.getInt("seed-village", 10387312);
this.desertSeed = this.getInt("seed-desert", 14357617);
this.iglooSeed = this.getInt("seed-igloo", 14357618);
@ -416,10 +384,8 @@ public class SpigotWorldConfig
public float swimMultiplier;
public float sprintMultiplier;
public float otherMultiplier;
private void initHunger()
{
if ( SpigotConfig.version < 10 )
{
private void initHunger() {
if (SpigotConfig.version < 10) {
this.set("hunger.walk-exhaustion", null);
this.set("hunger.sprint-exhaustion", null);
this.set("hunger.combat-exhaustion", 0.1);
@ -438,8 +404,7 @@ public class SpigotWorldConfig
public int currentPrimedTnt = 0;
public int maxTntTicksPerTick;
private void maxTntPerTick() {
if ( SpigotConfig.version < 7 )
{
if (SpigotConfig.version < 7) {
this.set("max-tnt-per-tick", 100);
}
this.maxTntTicksPerTick = this.getInt("max-tnt-per-tick", 100);
@ -447,23 +412,20 @@ public class SpigotWorldConfig
}
public int hangingTickFrequency;
private void hangingTickFrequency()
{
private void hangingTickFrequency() {
this.hangingTickFrequency = this.getInt("hanging-tick-frequency", 100);
}
public int tileMaxTickTime;
public int entityMaxTickTime;
private void maxTickTimes()
{
private void maxTickTimes() {
this.tileMaxTickTime = this.getInt("max-tick-time.tile", 50);
this.entityMaxTickTime = this.getInt("max-tick-time.entity", 50);
this.log("Tile Max Tick Time: " + this.tileMaxTickTime + "ms Entity max Tick Time: " + this.entityMaxTickTime + "ms");
}
public int thunderChance;
private void thunderChance()
{
private void thunderChance() {
this.thunderChance = this.getInt("thunder-chance", 100000);
}

View file

@ -5,8 +5,8 @@ public class TickLimiter {
private final int maxTime;
private long startTime;
public TickLimiter(int maxtime) {
this.maxTime = maxtime;
public TickLimiter(int maxTime) {
this.maxTime = maxTime;
}
public void initTick() {

View file

@ -1,51 +1,55 @@
package org.spigotmc;
import net.minecraft.server.MinecraftServer;
import org.bukkit.ChatColor;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.JoinConfiguration;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
public class TicksPerSecondCommand extends Command
{
import static net.kyori.adventure.text.Component.text;
public TicksPerSecondCommand(String name)
{
public class TicksPerSecondCommand extends Command {
private boolean hasShownMemoryWarning; // Paper
public TicksPerSecondCommand(String name) {
super(name);
this.description = "Gets the current ticks per second for the server";
this.usageMessage = "/tps";
this.setPermission("bukkit.command.tps");
}
// Paper start
private static final net.kyori.adventure.text.Component WARN_MSG = net.kyori.adventure.text.Component.text()
.append(net.kyori.adventure.text.Component.text("Warning: ", net.kyori.adventure.text.format.NamedTextColor.RED))
.append(net.kyori.adventure.text.Component.text("Memory usage on modern garbage collectors is not a stable value and it is perfectly normal to see it reach max. Please do not pay it much attention.", net.kyori.adventure.text.format.NamedTextColor.GOLD))
private static final Component WARN_MSG = text()
.append(text("Warning: ", NamedTextColor.RED))
.append(text("Memory usage on modern garbage collectors is not a stable value and it is perfectly normal to see it reach max. Please do not pay it much attention.", NamedTextColor.GOLD))
.build();
// Paper end
@Override
public boolean execute(CommandSender sender, String currentAlias, String[] args)
{
if ( !this.testPermission( sender ) )
{
public boolean execute(CommandSender sender, String currentAlias, String[] args) {
if (!this.testPermission(sender)) {
return true;
}
// Paper start - Further improve tick handling
double[] tps = org.bukkit.Bukkit.getTPS();
net.kyori.adventure.text.Component[] tpsAvg = new net.kyori.adventure.text.Component[tps.length];
Component[] tpsAvg = new Component[tps.length];
for (int i = 0; i < tps.length; i++) {
tpsAvg[i] = TicksPerSecondCommand.format(tps[i]);
}
net.kyori.adventure.text.TextComponent.Builder builder = net.kyori.adventure.text.Component.text();
builder.append(net.kyori.adventure.text.Component.text("TPS from last 1m, 5m, 15m: ", net.kyori.adventure.text.format.NamedTextColor.GOLD));
builder.append(net.kyori.adventure.text.Component.join(net.kyori.adventure.text.JoinConfiguration.commas(true), tpsAvg));
TextComponent.Builder builder = text();
builder.append(text("TPS from last 1m, 5m, 15m: ", NamedTextColor.GOLD));
builder.append(Component.join(JoinConfiguration.commas(true), tpsAvg));
sender.sendMessage(builder.asComponent());
if (args.length > 0 && args[0].equals("mem") && sender.hasPermission("bukkit.command.tpsmemory")) {
sender.sendMessage(net.kyori.adventure.text.Component.text()
.append(net.kyori.adventure.text.Component.text("Current Memory Usage: ", net.kyori.adventure.text.format.NamedTextColor.GOLD))
.append(net.kyori.adventure.text.Component.text(((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / (1024 * 1024)) + "/" + (Runtime.getRuntime().totalMemory() / (1024 * 1024)) + " mb (Max: " + (Runtime.getRuntime().maxMemory() / (1024 * 1024)) + " mb)", net.kyori.adventure.text.format.NamedTextColor.GREEN))
sender.sendMessage(text()
.append(text("Current Memory Usage: ", NamedTextColor.GOLD))
.append(text(((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / (1024 * 1024)) + "/" + (Runtime.getRuntime().totalMemory() / (1024 * 1024)) + " mb (Max: " + (Runtime.getRuntime().maxMemory() / (1024 * 1024)) + " mb)", NamedTextColor.GREEN))
);
if (!this.hasShownMemoryWarning) {
sender.sendMessage(WARN_MSG);
@ -57,13 +61,11 @@ public class TicksPerSecondCommand extends Command
return true;
}
private boolean hasShownMemoryWarning; // Paper
private static net.kyori.adventure.text.Component format(double tps) // Paper - Made static
{
// Paper
net.kyori.adventure.text.format.TextColor color = ( ( tps > 18.0 ) ? net.kyori.adventure.text.format.NamedTextColor.GREEN : ( tps > 16.0 ) ? net.kyori.adventure.text.format.NamedTextColor.YELLOW : net.kyori.adventure.text.format.NamedTextColor.RED );
private static Component format(double tps) { // Paper - Made static
// Paper start
TextColor color = ((tps > 18.0) ? NamedTextColor.GREEN : (tps > 16.0) ? NamedTextColor.YELLOW : NamedTextColor.RED);
String amount = Math.min(Math.round(tps * 100.0) / 100.0, 20.0) + (tps > 21.0 ? "*" : ""); // Paper - only print * at 21, we commonly peak to 20.02 as the tick sleep is not accurate enough, stop the noise
return net.kyori.adventure.text.Component.text(amount, color);
return text(amount, color);
// Paper end
}
}

View file

@ -18,9 +18,7 @@ public final class TrackingRange {
* Gets the range an entity should be 'tracked' by players and visible in
* the client.
*
* @param entity
* @param defaultRange Default range defined by Mojang
* @return
*/
public static int getEntityTrackingRange(final Entity entity, final int defaultRange) {
if (defaultRange == 0) {
@ -51,7 +49,7 @@ public final class TrackingRange {
} else {
if (entity instanceof net.minecraft.world.entity.boss.enderdragon.EnderDragon) {
// Exempt ender dragon
return ((ServerLevel) entity.getCommandSenderWorld()).getChunkSource().chunkMap.serverViewDistance;
return ((ServerLevel) entity.level()).getChunkSource().chunkMap.serverViewDistance;
}
return config.otherTrackingRange;
}

View file

@ -5,11 +5,12 @@ import java.lang.management.MonitorInfo;
import java.lang.management.ThreadInfo;
import java.util.logging.Level;
import java.util.logging.Logger;
import io.papermc.paper.configuration.GlobalConfiguration;
import net.minecraft.server.MinecraftServer;
import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.CraftServer;
public class WatchdogThread extends Thread
{
public class WatchdogThread extends Thread {
private static WatchdogThread instance;
private long timeoutTime;
@ -21,159 +22,140 @@ public class WatchdogThread extends Thread
private volatile long lastTick;
private volatile boolean stopping;
private WatchdogThread(long timeoutTime, boolean restart)
{
private WatchdogThread(long timeoutTime, boolean restart) {
super("Paper Watchdog Thread");
this.timeoutTime = timeoutTime;
this.restart = restart;
earlyWarningEvery = Math.min(io.papermc.paper.configuration.GlobalConfiguration.get().watchdog.earlyWarningEvery, timeoutTime); // Paper
earlyWarningDelay = Math.min(io.papermc.paper.configuration.GlobalConfiguration.get().watchdog.earlyWarningDelay, timeoutTime); // Paper
this.earlyWarningEvery = Math.min(GlobalConfiguration.get().watchdog.earlyWarningEvery, timeoutTime); // Paper
this.earlyWarningDelay = Math.min(GlobalConfiguration.get().watchdog.earlyWarningDelay, timeoutTime); // Paper
}
private static long monotonicMillis()
{
private static long monotonicMillis() {
return System.nanoTime() / 1000000L;
}
public static void doStart(int timeoutTime, boolean restart)
{
if ( WatchdogThread.instance == null )
{
public static void doStart(int timeoutTime, boolean restart) {
if (WatchdogThread.instance == null) {
WatchdogThread.instance = new WatchdogThread(timeoutTime * 1000L, restart);
WatchdogThread.instance.start();
} else
{
} else {
WatchdogThread.instance.timeoutTime = timeoutTime * 1000L;
WatchdogThread.instance.restart = restart;
}
}
public static void tick()
{
public static void tick() {
WatchdogThread.instance.lastTick = WatchdogThread.monotonicMillis();
}
public static void doStop()
{
if ( WatchdogThread.instance != null )
{
public static void doStop() {
if (WatchdogThread.instance != null) {
WatchdogThread.instance.stopping = true;
}
}
@Override
public void run()
{
while ( !this.stopping )
{
//
public void run() {
while (!this.stopping) {
// Paper start
Logger log = Bukkit.getServer().getLogger();
Logger logger = Bukkit.getServer().getLogger();
long currentTime = WatchdogThread.monotonicMillis();
if ( this.lastTick != 0 && this.timeoutTime > 0 && currentTime > this.lastTick + this.earlyWarningEvery && !Boolean.getBoolean("disable.watchdog")) // Paper - Add property to disable
{
boolean isLongTimeout = currentTime > lastTick + timeoutTime;
if (this.lastTick != 0 && this.timeoutTime > 0 && currentTime > this.lastTick + this.earlyWarningEvery && !Boolean.getBoolean("disable.watchdog")) { // Paper - Add property to disable
boolean isLongTimeout = currentTime > this.lastTick + this.timeoutTime;
// Don't spam early warning dumps
if ( !isLongTimeout && (earlyWarningEvery <= 0 || !hasStarted || currentTime < lastEarlyWarning + earlyWarningEvery || currentTime < lastTick + earlyWarningDelay)) continue;
if ( !isLongTimeout && MinecraftServer.getServer().hasStopped()) continue; // Don't spam early watchdog warnings during shutdown, we'll come back to this...
lastEarlyWarning = currentTime;
if (!isLongTimeout && (this.earlyWarningEvery <= 0 ||
!hasStarted || currentTime < this.lastEarlyWarning + this.earlyWarningEvery ||
currentTime < this.lastTick + this.earlyWarningDelay))
continue;
if (!isLongTimeout && MinecraftServer.getServer().hasStopped())
continue; // Don't spam early watchdog warnings during shutdown, we'll come back to this...
this.lastEarlyWarning = currentTime;
if (isLongTimeout) {
// Paper end
log.log( Level.SEVERE, "------------------------------" );
log.log( Level.SEVERE, "The server has stopped responding! This is (probably) not a Paper bug." ); // Paper
log.log( Level.SEVERE, "If you see a plugin in the Server thread dump below, then please report it to that author" );
log.log( Level.SEVERE, "\t *Especially* if it looks like HTTP or MySQL operations are occurring" );
log.log( Level.SEVERE, "If you see a world save or edit, then it means you did far more than your server can handle at once" );
log.log( Level.SEVERE, "\t If this is the case, consider increasing timeout-time in spigot.yml but note that this will replace the crash with LARGE lag spikes" );
log.log( Level.SEVERE, "If you are unsure or still think this is a Paper bug, please report this to https://github.com/PaperMC/Paper/issues" );
log.log( Level.SEVERE, "Be sure to include ALL relevant console errors and Minecraft crash reports" );
log.log( Level.SEVERE, "Paper version: " + Bukkit.getServer().getVersion() );
//
if ( net.minecraft.world.level.Level.lastPhysicsProblem != null )
{
log.log( Level.SEVERE, "------------------------------" );
log.log( Level.SEVERE, "During the run of the server, a physics stackoverflow was supressed" );
log.log( Level.SEVERE, "near " + net.minecraft.world.level.Level.lastPhysicsProblem );
logger.log(Level.SEVERE, "------------------------------");
logger.log(Level.SEVERE, "The server has stopped responding! This is (probably) not a Paper bug."); // Paper
logger.log(Level.SEVERE, "If you see a plugin in the Server thread dump below, then please report it to that author");
logger.log(Level.SEVERE, "\t *Especially* if it looks like HTTP or MySQL operations are occurring");
logger.log(Level.SEVERE, "If you see a world save or edit, then it means you did far more than your server can handle at once");
logger.log(Level.SEVERE, "\t If this is the case, consider increasing timeout-time in spigot.yml but note that this will replace the crash with LARGE lag spikes");
logger.log(Level.SEVERE, "If you are unsure or still think this is a Paper bug, please report this to https://github.com/PaperMC/Paper/issues");
logger.log(Level.SEVERE, "Be sure to include ALL relevant console errors and Minecraft crash reports");
logger.log(Level.SEVERE, "Paper version: " + Bukkit.getServer().getVersion());
if (net.minecraft.world.level.Level.lastPhysicsProblem != null) {
logger.log(Level.SEVERE, "------------------------------");
logger.log(Level.SEVERE, "During the run of the server, a physics stackoverflow was supressed");
logger.log(Level.SEVERE, "near " + net.minecraft.world.level.Level.lastPhysicsProblem);
}
//
// Paper start - Warn in watchdog if an excessive velocity was ever set
if (org.bukkit.craftbukkit.CraftServer.excessiveVelEx != null) {
log.log(Level.SEVERE, "------------------------------");
log.log(Level.SEVERE, "During the run of the server, a plugin set an excessive velocity on an entity");
log.log(Level.SEVERE, "This may be the cause of the issue, or it may be entirely unrelated");
log.log(Level.SEVERE, org.bukkit.craftbukkit.CraftServer.excessiveVelEx.getMessage());
for (StackTraceElement stack : org.bukkit.craftbukkit.CraftServer.excessiveVelEx.getStackTrace()) {
log.log( Level.SEVERE, "\t\t" + stack );
if (CraftServer.excessiveVelEx != null) {
logger.log(Level.SEVERE, "------------------------------");
logger.log(Level.SEVERE, "During the run of the server, a plugin set an excessive velocity on an entity");
logger.log(Level.SEVERE, "This may be the cause of the issue, or it may be entirely unrelated");
logger.log(Level.SEVERE, CraftServer.excessiveVelEx.getMessage());
for (StackTraceElement stack : CraftServer.excessiveVelEx.getStackTrace()) {
logger.log(Level.SEVERE, "\t\t" + stack);
}
}
// Paper end
} else
{
log.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PAPER - THIS IS NOT A BUG OR A CRASH - " + Bukkit.getServer().getVersion() + " ---");
log.log(Level.SEVERE, "The server has not responded for " + (currentTime - lastTick) / 1000 + " seconds! Creating thread dump");
} else {
logger.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PAPER - THIS IS NOT A BUG OR A CRASH - " + Bukkit.getServer().getVersion() + " ---");
logger.log(Level.SEVERE, "The server has not responded for " + (currentTime - lastTick) / 1000 + " seconds! Creating thread dump");
}
// Paper end - Different message for short timeout
log.log( Level.SEVERE, "------------------------------" );
log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):" ); // Paper
WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log );
log.log( Level.SEVERE, "------------------------------" );
//
logger.log(Level.SEVERE, "------------------------------");
logger.log(Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):"); // Paper
WatchdogThread.dumpThread(ManagementFactory.getThreadMXBean().getThreadInfo(MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE), logger);
logger.log(Level.SEVERE, "------------------------------");
// Paper start - Only print full dump on long timeouts
if ( isLongTimeout )
{
log.log( Level.SEVERE, "Entire Thread Dump:" );
if (isLongTimeout) {
logger.log(Level.SEVERE, "Entire Thread Dump:");
ThreadInfo[] threads = ManagementFactory.getThreadMXBean().dumpAllThreads(true, true);
for ( ThreadInfo thread : threads )
{
WatchdogThread.dumpThread( thread, log );
for (ThreadInfo thread : threads) {
WatchdogThread.dumpThread(thread, logger);
}
} else {
log.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PAPER - THIS IS NOT A BUG OR A CRASH ---");
logger.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PAPER - THIS IS NOT A BUG OR A CRASH ---");
}
log.log( Level.SEVERE, "------------------------------" );
logger.log(Level.SEVERE, "------------------------------");
if ( isLongTimeout )
{
if ( this.restart && !MinecraftServer.getServer().hasStopped() )
{
if (isLongTimeout) {
if (this.restart && !MinecraftServer.getServer().hasStopped()) {
RestartCommand.restart();
}
break;
} // Paper end
}
// Paper end
}
try
{
try {
sleep(1000); // Paper - Reduce check time to every second instead of every ten seconds, more consistent and allows for short timeout
} catch ( InterruptedException ex )
{
} catch (InterruptedException ex) {
this.interrupt();
}
}
}
private static void dumpThread(ThreadInfo thread, Logger log)
{
private static void dumpThread(ThreadInfo thread, Logger log) {
log.log(Level.SEVERE, "------------------------------");
//
log.log(Level.SEVERE, "Current Thread: " + thread.getThreadName());
log.log(Level.SEVERE, "\tPID: " + thread.getThreadId()
+ " | Suspended: " + thread.isSuspended()
+ " | Native: " + thread.isInNative()
+ " | State: " + thread.getThreadState());
if ( thread.getLockedMonitors().length != 0 )
{
if (thread.getLockedMonitors().length != 0) {
log.log(Level.SEVERE, "\tThread is waiting on monitor(s):");
for ( MonitorInfo monitor : thread.getLockedMonitors() )
{
for (MonitorInfo monitor : thread.getLockedMonitors()) {
log.log(Level.SEVERE, "\t\tLocked on:" + monitor.getLockedStackFrame());
}
}
log.log(Level.SEVERE, "\tStack:");
//
for ( StackTraceElement stack : io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateStacktrace(thread.getStackTrace()) ) // Paper
{
for (StackTraceElement stack : io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateStacktrace(thread.getStackTrace())) { // Paper
log.log(Level.SEVERE, "\t\t" + stack);
}
}