mirror of
https://github.com/PaperMC/Paper.git
synced 2026-11-04 17:00:09 -05:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17fbe152a6 | ||
|
|
8ac3459533 | ||
|
|
b72728d717 | ||
|
|
df17d86a37 |
No files matched your search
@@ -632,7 +632,10 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
|
||||
* @param spawnCategory the category of spawn
|
||||
* @return the default ticks per {@link SpawnCategory} mobs spawn value
|
||||
* @throws IllegalArgumentException if the category is {@link SpawnCategory#MISC}
|
||||
* @deprecated this is configured per world; use {@link World#getTicksPerSpawns(SpawnCategory)}.
|
||||
* This returns the primary world's value.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "26.3")
|
||||
public int getTicksPerSpawns(@NotNull SpawnCategory spawnCategory);
|
||||
|
||||
/**
|
||||
@@ -1908,7 +1911,10 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
|
||||
*
|
||||
* @param spawnCategory the category spawn
|
||||
* @return the {@link SpawnCategory} spawn limit
|
||||
* @deprecated this is configured per world; use {@link World#getSpawnLimit(SpawnCategory)}.
|
||||
* This returns the primary world's value.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "26.3")
|
||||
int getSpawnLimit(@NotNull SpawnCategory spawnCategory);
|
||||
|
||||
/**
|
||||
@@ -2421,60 +2427,6 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
|
||||
// Spigot start
|
||||
public class Spigot {
|
||||
|
||||
/**
|
||||
* @deprecated Server config options may be renamed or removed without notice. Prefer using existing API
|
||||
* wherever possible, rather than directly reading from a server config.
|
||||
*
|
||||
* @see #getServerConfig()
|
||||
* @return The server's spigot config.
|
||||
*/
|
||||
@Deprecated(since = "1.21.4", forRemoval = true)
|
||||
@NotNull
|
||||
public org.bukkit.configuration.file.YamlConfiguration getConfig() {
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Server config options may be renamed or removed without notice. Prefer using existing API
|
||||
* wherever possible, rather than directly reading from a server config.
|
||||
*
|
||||
* @see #getServerConfig()
|
||||
* @return The server's bukkit config.
|
||||
*/
|
||||
// Paper start
|
||||
@Deprecated(since = "1.21.4", forRemoval = true)
|
||||
@NotNull
|
||||
public org.bukkit.configuration.file.YamlConfiguration getBukkitConfig() {
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Server config options may be renamed or removed without notice. Prefer using existing API
|
||||
* wherever possible, rather than directly reading from a server config.
|
||||
*
|
||||
* @see #getServerConfig()
|
||||
* @return The server's spigot config.
|
||||
*/
|
||||
@Deprecated(since = "1.21.4", forRemoval = true)
|
||||
@NotNull
|
||||
public org.bukkit.configuration.file.YamlConfiguration getSpigotConfig() {
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Server config options may be renamed or removed without notice. Prefer using existing API
|
||||
* wherever possible, rather than directly reading from a server config.
|
||||
*
|
||||
* @see #getServerConfig()
|
||||
* @return The server's paper config.
|
||||
*/
|
||||
@Deprecated(since = "1.21.4", forRemoval = true)
|
||||
@NotNull
|
||||
public org.bukkit.configuration.file.YamlConfiguration getPaperConfig() {
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
// Paper end
|
||||
|
||||
/**
|
||||
* Sends the component to the player
|
||||
*
|
||||
@@ -2521,6 +2473,7 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
|
||||
*/
|
||||
void restart();
|
||||
|
||||
@Deprecated(forRemoval = true, since = "26.3")
|
||||
void reloadPermissions(); // Paper
|
||||
|
||||
boolean reloadCommandAliases(); // Paper
|
||||
@@ -2541,7 +2494,7 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
|
||||
* @deprecated use {@link #permissionMessage()}
|
||||
*/
|
||||
@NotNull
|
||||
@Deprecated
|
||||
@Deprecated(forRemoval = true)
|
||||
String getPermissionMessage();
|
||||
|
||||
/**
|
||||
|
||||
@@ -3472,7 +3472,7 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient
|
||||
* world
|
||||
* <p>
|
||||
* <b>Note:</b> If set to a negative number the world will use the
|
||||
* server-wide spawn limit instead.
|
||||
* spawn limit from its configuration instead.
|
||||
*
|
||||
* @param limit the new mob limit
|
||||
* @deprecated Deprecated in favor of {@link #setSpawnLimit(SpawnCategory, int)}
|
||||
@@ -3499,7 +3499,7 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient
|
||||
* world
|
||||
* <p>
|
||||
* <b>Note:</b> If set to a negative number the world will use the
|
||||
* server-wide spawn limit instead.
|
||||
* spawn limit from its configuration instead.
|
||||
*
|
||||
* @param limit the new mob limit
|
||||
* @deprecated Deprecated in favor of {@link #getSpawnLimit(SpawnCategory)}
|
||||
@@ -3526,7 +3526,7 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient
|
||||
* this world
|
||||
* <p>
|
||||
* <b>Note:</b> If set to a negative number the world will use the
|
||||
* server-wide spawn limit instead.
|
||||
* spawn limit from its configuration instead.
|
||||
*
|
||||
* @param limit the new mob limit
|
||||
* @deprecated Deprecated in favor of {@link #setSpawnLimit(SpawnCategory, int)}
|
||||
@@ -3553,7 +3553,7 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient
|
||||
* this world
|
||||
* <p>
|
||||
* <b>Note:</b> If set to a negative number the world will use the
|
||||
* server-wide spawn limit instead.
|
||||
* spawn limit from its configuration instead.
|
||||
*
|
||||
* @param limit the new mob limit
|
||||
* @deprecated Deprecated in favor of {@link #setSpawnLimit(SpawnCategory, int)}
|
||||
@@ -3580,7 +3580,7 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient
|
||||
* in this world
|
||||
* <p>
|
||||
* <b>Note:</b> If set to a negative number the world will use the
|
||||
* server-wide spawn limit instead.
|
||||
* spawn limit from its configuration instead.
|
||||
*
|
||||
* @param limit the new mob limit
|
||||
* @deprecated Deprecated in favor of {@link #setSpawnLimit(SpawnCategory, int)}
|
||||
@@ -3607,7 +3607,7 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient
|
||||
* this world
|
||||
* <p>
|
||||
* <b>Note:</b> If set to a negative number the world will use the
|
||||
* server-wide spawn limit instead.
|
||||
* spawn limit from its configuration instead.
|
||||
*
|
||||
* @param limit the new mob limit
|
||||
* @deprecated Deprecated in favor of {@link #setSpawnLimit(SpawnCategory, int)}
|
||||
@@ -3631,7 +3631,7 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient
|
||||
* this world
|
||||
* <p>
|
||||
* <b>Note:</b> If set to a negative number the world will use the
|
||||
* server-wide spawn limit instead.
|
||||
* spawn limit from its configuration instead.
|
||||
*
|
||||
* @param spawnCategory the entity category
|
||||
* @param limit the new mob limit
|
||||
|
||||
@@ -136,7 +136,7 @@ dependencies {
|
||||
implementation("io.netty:netty-codec-haproxy:4.2.16.Final") // Add support for proxy protocol
|
||||
implementation("org.apache.logging.log4j:log4j-iostreams:2.26.0")
|
||||
implementation("org.ow2.asm:asm-commons:9.9.1")
|
||||
implementation("org.spongepowered:configurate-yaml:4.2.0")
|
||||
implementation("org.spongepowered:configurate-core:4.2.0") // plus GeyserMC's yaml loader with comments in io.papermc.paper.configuration.yaml
|
||||
|
||||
// Deps that were previously in the API but have now been moved here for backwards compat, eventually to be removed
|
||||
runtimeOnly("commons-lang:commons-lang:2.6")
|
||||
|
||||
+1707
File diff suppressed because it is too large.
Load diff
@@ -12,6 +12,8 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.framework.qual.DefaultQualifier;
|
||||
import org.spigotmc.RestartCommand;
|
||||
import org.spigotmc.TicksPerSecondCommand;
|
||||
|
||||
@DefaultQualifier(NonNull.class)
|
||||
public final class PaperCommands {
|
||||
@@ -23,6 +25,8 @@ public final class PaperCommands {
|
||||
|
||||
public static void registerCommands(final MinecraftServer server) {
|
||||
COMMANDS.put("paper", new PaperCommand("paper"));
|
||||
COMMANDS.put("tps", new TicksPerSecondCommand("tps")); // from spigot
|
||||
COMMANDS.put("restart", new RestartCommand("restart")); // from spigot
|
||||
|
||||
COMMANDS.forEach((s, command) -> {
|
||||
server.server.getCommandMap().register(s, "Paper", command);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package io.papermc.paper.command.subcommands;
|
||||
|
||||
import io.papermc.paper.command.PaperSubcommand;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.craftbukkit.CraftServer;
|
||||
@@ -24,9 +23,9 @@ public final class ReloadCommand implements PaperSubcommand {
|
||||
Command.broadcastCommandMessage(sender, text("Please note that this command is not supported and may cause issues.", RED));
|
||||
Command.broadcastCommandMessage(sender, text("If you encounter any issues please use the /stop command to restart your server.", RED));
|
||||
|
||||
MinecraftServer server = ((CraftServer) sender.getServer()).getServer();
|
||||
server.paperConfigurations.reloadConfigs(server);
|
||||
server.server.reloadCount++;
|
||||
final CraftServer server = (CraftServer) sender.getServer();
|
||||
server.reloadPaperConfiguration();
|
||||
server.reloadCount++;
|
||||
|
||||
Command.broadcastCommandMessage(sender, text("Paper config reload complete.", GREEN));
|
||||
}
|
||||
|
||||
@@ -2,11 +2,10 @@ package io.papermc.paper.configuration;
|
||||
|
||||
public final class Configuration {
|
||||
public static final String VERSION_FIELD = "_version";
|
||||
@Deprecated
|
||||
public static final String LEGACY_CONFIG_VERSION_FIELD = "config-version";
|
||||
|
||||
@Deprecated
|
||||
public static final int FINAL_LEGACY_VERSION = 27;
|
||||
public static final int FINAL_LEGACY_SPIGOT_VERSION = 13;
|
||||
|
||||
private Configuration() {
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package io.papermc.paper.configuration;
|
||||
|
||||
import io.papermc.paper.configuration.yaml.BlankLineStyle;
|
||||
import io.papermc.paper.configuration.yaml.NodeStyle;
|
||||
import io.papermc.paper.configuration.yaml.YamlConfigurationLoader;
|
||||
import java.nio.file.Path;
|
||||
import org.spongepowered.configurate.loader.HeaderMode;
|
||||
import org.spongepowered.configurate.util.MapFactories;
|
||||
import org.spongepowered.configurate.yaml.NodeStyle;
|
||||
import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
|
||||
|
||||
public final class ConfigurationLoaders {
|
||||
private ConfigurationLoaders() {
|
||||
@@ -15,6 +16,8 @@ public final class ConfigurationLoaders {
|
||||
.indent(2)
|
||||
.nodeStyle(NodeStyle.BLOCK)
|
||||
.headerMode(HeaderMode.PRESET)
|
||||
.commentsEnabled(true)
|
||||
.blankLineStyle(BlankLineStyle.AFTER_NESTED)
|
||||
.defaultOptions(options -> options.mapFactory(MapFactories.sortedNatural()));
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ import com.mojang.logging.LogUtils;
|
||||
import io.leangen.geantyref.TypeToken;
|
||||
import io.papermc.paper.configuration.constraint.Constraint;
|
||||
import io.papermc.paper.configuration.constraint.Constraints;
|
||||
import io.papermc.paper.configuration.transformation.ServerPropertiesAccess;
|
||||
import io.papermc.paper.configuration.yaml.YamlConfigurationLoader;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.file.AccessDeniedException;
|
||||
@@ -14,9 +16,11 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.UnaryOperator;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.server.dedicated.DedicatedServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.gamerules.GameRules;
|
||||
import org.jetbrains.annotations.MustBeInvokedByOverriders;
|
||||
@@ -29,7 +33,6 @@ import org.spongepowered.configurate.ConfigurationOptions;
|
||||
import org.spongepowered.configurate.objectmapping.ObjectMapper;
|
||||
import org.spongepowered.configurate.serialize.SerializationException;
|
||||
import org.spongepowered.configurate.util.CheckedFunction;
|
||||
import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
|
||||
|
||||
public abstract class Configurations<G, W> {
|
||||
|
||||
@@ -59,10 +62,18 @@ public abstract class Configurations<G, W> {
|
||||
}
|
||||
|
||||
protected ObjectMapper.Factory.Builder createObjectMapper() {
|
||||
return ObjectMapper.factoryBuilder()
|
||||
return defaultObjectMapperFactoryBuilder();
|
||||
}
|
||||
|
||||
static ObjectMapper.Factory.Builder defaultObjectMapperFactoryBuilder() {
|
||||
return addDefaultConstraints(ObjectMapper.factoryBuilder());
|
||||
}
|
||||
|
||||
static ObjectMapper.Factory.Builder addDefaultConstraints(final ObjectMapper.Factory.Builder builder) {
|
||||
return builder
|
||||
.addConstraint(Constraint.class, new Constraint.Factory())
|
||||
.addConstraint(Constraints.Min.class, Number.class, new Constraints.Min.Factory())
|
||||
.addConstraint(Constraints.Max.class, Number.class, new Constraints.Max.Factory());
|
||||
.addConstraint(Constraints.Min.class, new Constraints.Min.Factory())
|
||||
.addConstraint(Constraints.Max.class, new Constraints.Max.Factory());
|
||||
}
|
||||
|
||||
protected YamlConfigurationLoader.Builder createLoaderBuilder() {
|
||||
@@ -80,7 +91,7 @@ public abstract class Configurations<G, W> {
|
||||
}
|
||||
|
||||
@MustBeInvokedByOverriders
|
||||
protected YamlConfigurationLoader.Builder createGlobalLoaderBuilder(RegistryAccess registryAccess) {
|
||||
protected YamlConfigurationLoader.Builder createGlobalLoaderBuilder(final ContextMap contextMap) {
|
||||
return this.createLoaderBuilder();
|
||||
}
|
||||
|
||||
@@ -103,8 +114,8 @@ public abstract class Configurations<G, W> {
|
||||
};
|
||||
}
|
||||
|
||||
public G initializeGlobalConfiguration(final RegistryAccess registryAccess) throws ConfigurateException {
|
||||
return this.initializeGlobalConfiguration(registryAccess, creator(this.globalConfigClass, true));
|
||||
public G initializeGlobalConfiguration(final DedicatedServer server) throws ConfigurateException {
|
||||
return this.initializeGlobalConfiguration(this.createGlobalContextMap(server).build(), creator(this.globalConfigClass, true));
|
||||
}
|
||||
|
||||
private void trySaveFileNode(YamlConfigurationLoader loader, ConfigurationNode node, String filename) throws ConfigurateException {
|
||||
@@ -117,9 +128,9 @@ public abstract class Configurations<G, W> {
|
||||
}
|
||||
}
|
||||
|
||||
protected G initializeGlobalConfiguration(final RegistryAccess registryAccess, final CheckedFunction<ConfigurationNode, G, SerializationException> creator) throws ConfigurateException {
|
||||
protected G initializeGlobalConfiguration(final ContextMap contextMap, final CheckedFunction<ConfigurationNode, G, SerializationException> creator) throws ConfigurateException {
|
||||
final Path configFile = this.globalFolder.resolve(this.globalConfigFileName);
|
||||
final YamlConfigurationLoader loader = this.createGlobalLoaderBuilder(registryAccess)
|
||||
final YamlConfigurationLoader loader = this.createGlobalLoaderBuilder(contextMap)
|
||||
.defaultOptions(this.applyObjectMapperFactory(this.createGlobalObjectMapperFactoryBuilder().build()))
|
||||
.path(configFile)
|
||||
.build();
|
||||
@@ -132,9 +143,9 @@ public abstract class Configurations<G, W> {
|
||||
node = loader.load();
|
||||
this.verifyGlobalConfigVersion(node);
|
||||
}
|
||||
this.applyGlobalConfigTransformations(node);
|
||||
this.applyGlobalConfigTransformations(contextMap, node);
|
||||
final G instance = creator.apply(node);
|
||||
trySaveFileNode(loader, node, configFile.toString());
|
||||
this.trySaveFileNode(loader, node, configFile.toString());
|
||||
return instance;
|
||||
}
|
||||
|
||||
@@ -148,7 +159,14 @@ public abstract class Configurations<G, W> {
|
||||
}
|
||||
}
|
||||
|
||||
protected void applyGlobalConfigTransformations(final ConfigurationNode node) throws ConfigurateException {
|
||||
protected void applyGlobalConfigTransformations(final ContextMap contextMap, final ConfigurationNode node) throws ConfigurateException {
|
||||
}
|
||||
|
||||
@MustBeInvokedByOverriders
|
||||
protected ContextMap.Builder createGlobalContextMap(final DedicatedServer server) {
|
||||
return ContextMap.builder()
|
||||
.put(REGISTRY_ACCESS, server.registryAccess())
|
||||
.put(SERVER_PROPERTIES, ServerPropertiesAccess.of(server));
|
||||
}
|
||||
|
||||
@MustBeInvokedByOverriders
|
||||
@@ -268,8 +286,12 @@ public abstract class Configurations<G, W> {
|
||||
}
|
||||
|
||||
private UnaryOperator<ConfigurationOptions> applyObjectMapperFactory(final ObjectMapper.Factory factory) {
|
||||
return options -> options.serializers(builder -> builder
|
||||
.register(this::isConfigType, factory.asTypeSerializer())
|
||||
return options -> applyObjectMapperFactory(options, this::isConfigType, factory);
|
||||
}
|
||||
|
||||
static ConfigurationOptions applyObjectMapperFactory(final ConfigurationOptions options, final Predicate<Type> isConfigType, final ObjectMapper.Factory factory) {
|
||||
return options.serializers(builder -> builder
|
||||
.register(isConfigType, factory.asTypeSerializer())
|
||||
.registerAnnotatedObjects(factory));
|
||||
}
|
||||
|
||||
@@ -342,6 +364,7 @@ public abstract class Configurations<G, W> {
|
||||
public static final ContextKey<Void> FIRST_DEFAULT = new ContextKey<>(Void.class, "first default");
|
||||
public static final ContextKey<RegistryAccess> REGISTRY_ACCESS = new ContextKey<>(RegistryAccess.class, "registry access");
|
||||
public static final ContextKey<GameRules> GAME_RULES = new ContextKey<>(GameRules.class, "game rules");
|
||||
public static final ContextKey<ServerPropertiesAccess> SERVER_PROPERTIES = new ContextKey<>(ServerPropertiesAccess.class, "server properties");
|
||||
|
||||
public record ContextKey<T>(TypeToken<T> type, String name) {
|
||||
|
||||
|
||||
+197
-11
@@ -1,22 +1,38 @@
|
||||
package io.papermc.paper.configuration;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import io.papermc.paper.configuration.constraint.Constraint;
|
||||
import io.papermc.paper.configuration.constraint.Constraints;
|
||||
import io.papermc.paper.configuration.mapping.LoadEarly;
|
||||
import io.papermc.paper.configuration.mapping.MergeMap;
|
||||
import io.papermc.paper.configuration.serializer.collection.map.WriteKeyBack;
|
||||
import io.papermc.paper.configuration.type.number.DoubleOr;
|
||||
import io.papermc.paper.configuration.type.number.IntOr;
|
||||
import io.papermc.paper.util.sanitizer.ItemObfuscationBinding;
|
||||
import io.papermc.paper.util.sanitizer.OversizedItemComponentSanitizer;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.OptionalInt;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.ServerboundPlaceRecipePacket;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.world.entity.ai.attributes.Attribute;
|
||||
import net.minecraft.world.entity.ai.attributes.Attributes;
|
||||
import net.minecraft.world.entity.ai.attributes.RangedAttribute;
|
||||
import org.apache.logging.log4j.Level;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.core.LoggerContext;
|
||||
import org.bukkit.Warning;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
@@ -28,8 +44,9 @@ import org.spongepowered.configurate.objectmapping.meta.Setting;
|
||||
@SuppressWarnings({"CanBeFinal", "FieldCanBeLocal", "FieldMayBeFinal", "NotNullFieldNotInitialized", "InnerClassMayBeStatic"})
|
||||
public class GlobalConfiguration extends ConfigurationPart {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
static final int CURRENT_VERSION = 31; // (when you change the version, change the comment, so it conflicts on rebases): allow-nether property to config
|
||||
static final int CURRENT_VERSION = 32; // (when you change the version, change the comment, so it conflicts on rebases): merge spigot.yml and bukkit.yml
|
||||
private static GlobalConfiguration instance;
|
||||
private static @Nullable Level levelBeforeDebug; // root log level to restore once logging.debug is turned off again
|
||||
public static boolean isFirstStart = false;
|
||||
public static GlobalConfiguration get() {
|
||||
return instance;
|
||||
@@ -90,10 +107,18 @@ public class GlobalConfiguration extends ConfigurationPart {
|
||||
public Component connectionThrottle = Component.text("Connection throttled! Please wait before reconnecting.");
|
||||
public Component flyingPlayer = Component.translatable("multiplayer.disconnect.flying");
|
||||
public Component flyingVehicle = Component.translatable("multiplayer.disconnect.flying");
|
||||
public Component whitelist = Component.translatable("multiplayer.disconnect.not_whitelisted");
|
||||
public Component serverFull = Component.translatable("multiplayer.disconnect.server_full");
|
||||
public Component outdatedClient = Component.translatable("multiplayer.disconnect.outdated_client");
|
||||
public Component outdatedServer = Component.translatable("multiplayer.disconnect.outdated_server");
|
||||
public Component restart = Component.text("Server is restarting");
|
||||
}
|
||||
|
||||
public Component noPermission = Component.text("I'm sorry, but you do not have permission to perform this command. Please contact the server administrators if you believe that this is in error.", NamedTextColor.RED);
|
||||
public boolean useDisplayNameInQuitMessage = false;
|
||||
public boolean sendCommandParseFailureMessage = true;
|
||||
@Comment("Broadcast to players when the server shuts down.")
|
||||
public Component shutdown = Component.text("Server closed");
|
||||
}
|
||||
|
||||
public Spark spark;
|
||||
@@ -109,6 +134,7 @@ public class GlobalConfiguration extends ConfigurationPart {
|
||||
public BungeeCord bungeeCord;
|
||||
|
||||
public class BungeeCord extends ConfigurationPart {
|
||||
public boolean enabled = false;
|
||||
public boolean onlineMode = true;
|
||||
}
|
||||
|
||||
@@ -136,7 +162,15 @@ public class GlobalConfiguration extends ConfigurationPart {
|
||||
}
|
||||
public boolean proxyProtocol = false;
|
||||
public boolean isProxyOnlineMode() {
|
||||
return org.bukkit.Bukkit.getOnlineMode() || (org.spigotmc.SpigotConfig.bungee && this.bungeeCord.onlineMode) || (this.velocity.enabled && this.velocity.onlineMode);
|
||||
return org.bukkit.Bukkit.getOnlineMode() || (this.bungeeCord.enabled && this.bungeeCord.onlineMode) || (this.velocity.enabled && this.velocity.onlineMode);
|
||||
}
|
||||
|
||||
@PostProcess
|
||||
private void postProcess() {
|
||||
if (this.bungeeCord.enabled && this.velocity.enabled) {
|
||||
LOGGER.error("Both BungeeCord and Velocity forwarding are enabled, but only one can be used. Disabling BungeeCord...");
|
||||
this.bungeeCord.enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,6 +185,9 @@ public class GlobalConfiguration extends ConfigurationPart {
|
||||
public Watchdog watchdog;
|
||||
|
||||
public class Watchdog extends ConfigurationPart {
|
||||
public int timeoutSeconds = 60;
|
||||
public boolean restartOnCrash = true;
|
||||
public String restartScript = "./start.sh";
|
||||
public int earlyWarningEvery = 5000;
|
||||
public int earlyWarningDelay = 10000;
|
||||
}
|
||||
@@ -163,6 +200,13 @@ public class GlobalConfiguration extends ConfigurationPart {
|
||||
public int recipeSpamIncrement = 1;
|
||||
public int recipeSpamLimit = 20;
|
||||
public IntOr.Disabled incomingPacketThreshold = new IntOr.Disabled(OptionalInt.of(300));
|
||||
|
||||
@Comment("Commands that do not count towards the chat spam limit, matched by prefix.")
|
||||
public Commands commands;
|
||||
public class Commands extends ConfigurationPart {
|
||||
public boolean enabled = false;
|
||||
public Set<String> exclusions = Set.of("/skill");
|
||||
}
|
||||
}
|
||||
|
||||
public UnsupportedSettings unsupportedSettings;
|
||||
@@ -193,6 +237,12 @@ public class GlobalConfiguration extends ConfigurationPart {
|
||||
public boolean suggestPlayerNamesWhenNullTabCompletions = true;
|
||||
@Comment("Allow mounting entities to a player in the Vanilla '/ride' command.")
|
||||
public boolean rideCommandAllowPlayerAsVehicle = false;
|
||||
@Comment("Send commands with namespace prefixes to clients")
|
||||
public boolean sendNamespacedCommands = true;
|
||||
@Comment("Send tab completions to clients")
|
||||
public boolean tabCompletion = true;
|
||||
@Comment("Hide command block output from the console, regardless of the commandBlockOutput game rule.")
|
||||
public boolean silentCommandBlockConsole = false;
|
||||
}
|
||||
|
||||
public Time time;
|
||||
@@ -201,6 +251,38 @@ public class GlobalConfiguration extends ConfigurationPart {
|
||||
public boolean affectsAllWorlds = false;
|
||||
}
|
||||
|
||||
public Logging logging;
|
||||
|
||||
public class Logging extends ConfigurationPart {
|
||||
@Comment("Raise the root logger to trace level. Very noisy; only useful when debugging the server itself.")
|
||||
public boolean debug = false;
|
||||
@Comment("Log the execution of commands")
|
||||
public boolean commandExecution = true;
|
||||
@Comment("Log the deaths of villagers")
|
||||
public boolean villagerDeaths = true;
|
||||
@Comment("Log the deaths of named living entities")
|
||||
public boolean namedLivingEntityDeaths = true;
|
||||
|
||||
@PostProcess
|
||||
private void postProcess() {
|
||||
final LoggerContext context = (LoggerContext) LogManager.getContext(false);
|
||||
final org.apache.logging.log4j.core.config.Configuration configuration = context.getConfiguration();
|
||||
final org.apache.logging.log4j.core.config.LoggerConfig root = configuration.getLoggerConfig(LogManager.ROOT_LOGGER_NAME);
|
||||
if (this.debug && levelBeforeDebug == null && !LogManager.getRootLogger().isTraceEnabled()) {
|
||||
levelBeforeDebug = root.getLevel();
|
||||
root.setLevel(Level.ALL);
|
||||
context.updateLoggers(configuration);
|
||||
} else if (!this.debug && levelBeforeDebug != null) {
|
||||
root.setLevel(levelBeforeDebug);
|
||||
levelBeforeDebug = null;
|
||||
context.updateLoggers(configuration);
|
||||
}
|
||||
if (LogManager.getRootLogger().isTraceEnabled()) {
|
||||
LOGGER.info("Debug logging is enabled");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Scoreboards scoreboards;
|
||||
|
||||
public class Scoreboards extends ConfigurationPart {
|
||||
@@ -215,6 +297,8 @@ public class GlobalConfiguration extends ConfigurationPart {
|
||||
|
||||
public int ioThreads = -1;
|
||||
public int workerThreads = -1;
|
||||
@Comment("Ticks a chunk stays loaded after a plugin ticket for it is added.")
|
||||
public int pluginTicketTimeout = 600;
|
||||
|
||||
@PostProcess
|
||||
private void postProcess() {
|
||||
@@ -270,6 +354,16 @@ public class GlobalConfiguration extends ConfigurationPart {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Comment("""
|
||||
Plugin-provided generators, keyed by world name. Read while a world is being created, before its
|
||||
own config exists, which is why these live here rather than in the world config.""")
|
||||
public Map<String, WorldGenerators> worldGenerators = Map.of();
|
||||
|
||||
@ConfigSerializable
|
||||
public record WorldGenerators(@Nullable String generator, @Nullable String biomeProvider) {
|
||||
}
|
||||
|
||||
public Collisions collisions;
|
||||
|
||||
public class Collisions extends ConfigurationPart {
|
||||
@@ -277,17 +371,26 @@ public class GlobalConfiguration extends ConfigurationPart {
|
||||
public boolean sendFullPosForHardCollidingEntities = true;
|
||||
}
|
||||
|
||||
public PlayerAutoSave playerAutoSave;
|
||||
public Players players;
|
||||
|
||||
public class Players extends ConfigurationPart {
|
||||
|
||||
public class PlayerAutoSave extends ConfigurationPart {
|
||||
public int rate = -1;
|
||||
private int maxPerTick = -1;
|
||||
public int maxPerTick() {
|
||||
if (this.maxPerTick < 0) {
|
||||
return (this.rate == 1 || this.rate > 100) ? 10 : 20;
|
||||
public boolean disableSaving = false;
|
||||
public AutoSave autoSave;
|
||||
public int userCacheSize = 1000;
|
||||
public boolean saveUserCacheOnStopOnly = false;
|
||||
@Constraint(Constraints.Positive.class)
|
||||
public int sampleCount = 12;
|
||||
public int connectionShuffle = 0;
|
||||
public class AutoSave extends ConfigurationPart {
|
||||
public int rate = -1;
|
||||
private int maxPerTick = -1;
|
||||
public int maxPerTick() {
|
||||
if (this.maxPerTick < 0) {
|
||||
return (this.rate == 1 || this.rate > 100) ? 10 : 20;
|
||||
}
|
||||
return this.maxPerTick;
|
||||
}
|
||||
return this.maxPerTick;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,10 +426,14 @@ public class GlobalConfiguration extends ConfigurationPart {
|
||||
public boolean loadPermissionsYmlBeforePlugins = true;
|
||||
@Constraints.Min(4)
|
||||
public int regionFileCacheSize = 256;
|
||||
@Comment("""
|
||||
Whether chunks generated before 1.18 get the extended world height applied when they are next loaded.
|
||||
Global rather than per-world: it is also consulted by the world upgrader and the legacy structure
|
||||
file fix, which run over region files without a loaded world.""")
|
||||
public boolean belowZeroGenerationInExistingChunks = true;
|
||||
@Comment("See https://luckformula.emc.gs")
|
||||
public boolean useAlternativeLuckFormula = false;
|
||||
public boolean useDimensionTypeForCustomSpawners = false;
|
||||
public boolean strictAdvancementDimensionCheck = false;
|
||||
public IntOr.Default compressionLevel = IntOr.Default.USE_DEFAULT;
|
||||
@Comment("Defines the leniency distance added on the server to the interaction range of a player when validating interact packets.")
|
||||
public DoubleOr.Default clientInteractionLeniencyDistance = DoubleOr.Default.USE_DEFAULT;
|
||||
@@ -335,10 +442,46 @@ public class GlobalConfiguration extends ConfigurationPart {
|
||||
public IntOr.Default xpOrbGroupsPerArea = IntOr.Default.USE_DEFAULT;
|
||||
@Comment("See Fix MC-163962; prevent villager demand from going negative.")
|
||||
public boolean preventNegativeVillagerDemand = false;
|
||||
@Comment("""
|
||||
Ticks between server-wide saves of global data and player data.
|
||||
Per-world chunk saving is configured separately, with chunks.auto-save-interval in the world config.""")
|
||||
public int autoSaveInterval = 6000;
|
||||
@Comment("Whether the nether dimension is enabled and will be loaded.")
|
||||
public boolean enableNether = true;
|
||||
@Comment("Whether the end dimension is enabled and will be loaded.")
|
||||
public boolean enableEnd = true;
|
||||
@Comment("File that plugin permissions are loaded from, relative to the server root.")
|
||||
public String permissionsFile = "permissions.yml";
|
||||
@LoadEarly
|
||||
@Comment("""
|
||||
The directory inside the plugins folder that plugin updates are loaded from. Set to an empty string to disable auto updating.
|
||||
Read before plugins are discovered, so changes require a restart.""")
|
||||
public String updateFolder = "update";
|
||||
@LoadEarly
|
||||
@Comment("""
|
||||
The directory worlds are stored in, relative to the server root.
|
||||
Read before the world folder is opened, so changes require a restart. Overridden by the --universe launch argument.""")
|
||||
public String worldContainer = ".";
|
||||
@Comment("Milliseconds a connecting player must wait before reconnecting. Set to -1 to disable.")
|
||||
public int connectionThrottle = 4000;
|
||||
@Comment("Whether the plugin list is included in GS4 query responses.")
|
||||
public boolean queryPlugins = true;
|
||||
@Comment("""
|
||||
Whether deprecation warnings are printed when a plugin uses a deprecated API.
|
||||
DEFAULT defers to the plugin's own author-declared preference.""")
|
||||
public Warning.WarningState deprecatedVerbose = Warning.WarningState.DEFAULT;
|
||||
@Comment("Lowest plugin API version the server will load. 'none' allows any.")
|
||||
public String minimumApi = "none";
|
||||
@Comment("Cache map colours between renders. Uses more memory but is faster.")
|
||||
public boolean useMapColorCache = true;
|
||||
@Comment("Fix for MC-301114. This removes the oldest combat entry when it hits the cap, to fix a memory leak on constant entity damage.")
|
||||
public IntOr.Disabled maxTrackingCombatEntries = new IntOr.Disabled(OptionalInt.of(10240));
|
||||
public int nettyThreads = 4;
|
||||
|
||||
@PostProcess
|
||||
private void postProcess() {
|
||||
System.setProperty("io.netty.eventLoopThreads", Integer.toString(this.nettyThreads));
|
||||
}
|
||||
}
|
||||
|
||||
public BlockUpdates blockUpdates;
|
||||
@@ -354,6 +497,9 @@ public class GlobalConfiguration extends ConfigurationPart {
|
||||
|
||||
public class Anticheat extends ConfigurationPart {
|
||||
|
||||
public double movedWronglyThreshold = 0.0625D;
|
||||
public double movedTooQuicklyMultiplier = 10.0D;
|
||||
|
||||
public Obfuscation obfuscation;
|
||||
|
||||
public class Obfuscation extends ConfigurationPart {
|
||||
@@ -392,4 +538,44 @@ public class GlobalConfiguration extends ConfigurationPart {
|
||||
public class UpdateChecker extends ConfigurationPart {
|
||||
public boolean enabled = true;
|
||||
}
|
||||
|
||||
public Advancements advancements;
|
||||
public class Advancements extends ConfigurationPart {
|
||||
public boolean strictDimensionCheck = false;
|
||||
public boolean disableSaving = false;
|
||||
@LoadEarly
|
||||
@Comment("""
|
||||
Advancements that will not be loaded at all. Accepts '*' to disable every advancement,
|
||||
a namespace to disable all advancements in it, or individual advancement keys.
|
||||
Read before datapacks are loaded, so changes require a restart.""")
|
||||
public Set<String> disabled = Set.of("minecraft:story/disabled");
|
||||
}
|
||||
|
||||
public Stats stats;
|
||||
public class Stats extends ConfigurationPart {
|
||||
public boolean disableSaving = false;
|
||||
public Object2IntMap<Holder<Identifier>> forcedCustomStatValues = new Object2IntOpenHashMap<>();
|
||||
}
|
||||
|
||||
public AttributesSection attributes;
|
||||
public class AttributesSection extends ConfigurationPart {
|
||||
@ConfigSerializable
|
||||
public record AttributeOverride(double max) {
|
||||
}
|
||||
|
||||
@MergeMap
|
||||
public Map<Holder<Attribute>, AttributeOverride> overrides = Stream.of(Attributes.MAX_ABSORPTION, Attributes.MAX_HEALTH, Attributes.MOVEMENT_SPEED, Attributes.ATTACK_DAMAGE)
|
||||
.collect(Collectors.toMap(Function.identity(), a -> new AttributeOverride(((RangedAttribute) a.value()).maxValue)));
|
||||
|
||||
@PostProcess
|
||||
private void postProcess() {
|
||||
this.overrides.forEach((attribute, override) -> {
|
||||
if (attribute.value() instanceof final RangedAttribute ranged) {
|
||||
ranged.maxValue = override.max();
|
||||
} else {
|
||||
LOGGER.warn("Ignoring attribute max override for {}, it is not a ranged attribute", attribute.getRegisteredName());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
+142
-167
@@ -1,13 +1,12 @@
|
||||
package io.papermc.paper.configuration;
|
||||
|
||||
import com.google.common.base.Suppliers;
|
||||
import com.google.common.collect.Table;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import io.leangen.geantyref.TypeToken;
|
||||
import io.papermc.paper.configuration.legacy.RequiresSpigotInitialization;
|
||||
import io.papermc.paper.configuration.mapping.Definition;
|
||||
import io.papermc.paper.configuration.mapping.FieldProcessor;
|
||||
import io.papermc.paper.configuration.mapping.InnerClassFieldDiscoverer;
|
||||
import io.papermc.paper.configuration.mapping.LoadEarly;
|
||||
import io.papermc.paper.configuration.mapping.MergeMap;
|
||||
import io.papermc.paper.configuration.serializer.ComponentSerializer;
|
||||
import io.papermc.paper.configuration.serializer.EnumValueSerializer;
|
||||
@@ -20,15 +19,16 @@ import io.papermc.paper.configuration.serializer.collection.map.FastutilMapSeria
|
||||
import io.papermc.paper.configuration.serializer.collection.map.MapSerializer;
|
||||
import io.papermc.paper.configuration.serializer.registry.RegistryHolderSerializer;
|
||||
import io.papermc.paper.configuration.serializer.registry.RegistryValueSerializer;
|
||||
import io.papermc.paper.configuration.transformation.ContextualTransformation;
|
||||
import io.papermc.paper.configuration.transformation.Transformations;
|
||||
import io.papermc.paper.configuration.transformation.global.LegacyPaperConfig;
|
||||
import io.papermc.paper.configuration.transformation.global.versioned.V29_LogIPs;
|
||||
import io.papermc.paper.configuration.transformation.global.versioned.V30_PacketIds;
|
||||
import io.papermc.paper.configuration.transformation.global.versioned.V31_AllowNetherPropertiesToConfig;
|
||||
import io.papermc.paper.configuration.transformation.global.versioned.V32_SpigotConfigCleanup;
|
||||
import io.papermc.paper.configuration.transformation.world.FeatureSeedsGeneration;
|
||||
import io.papermc.paper.configuration.transformation.world.LegacyPaperWorldConfig;
|
||||
import io.papermc.paper.configuration.transformation.world.versioned.V29_ZeroWorldHeight;
|
||||
import io.papermc.paper.configuration.transformation.world.versioned.V30_RenameFilterNbtFromSpawnEgg;
|
||||
import io.papermc.paper.configuration.transformation.world.versioned.V32_WorldConfigCleanup;
|
||||
import io.papermc.paper.configuration.type.BooleanOrDefault;
|
||||
import io.papermc.paper.configuration.type.DespawnRange;
|
||||
import io.papermc.paper.configuration.type.Duration;
|
||||
@@ -37,54 +37,54 @@ import io.papermc.paper.configuration.type.EngineMode;
|
||||
import io.papermc.paper.configuration.type.fallback.FallbackValueSerializer;
|
||||
import io.papermc.paper.configuration.type.number.DoubleOr;
|
||||
import io.papermc.paper.configuration.type.number.IntOr;
|
||||
import io.papermc.paper.configuration.type.number.LongOr;
|
||||
import io.papermc.paper.configuration.yaml.YamlConfigurationLoader;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2LongMap;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2LongOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.core.component.DataComponentType;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.dedicated.DedicatedServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.entity.ai.attributes.Attribute;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.gamerules.GameRules;
|
||||
import net.minecraft.world.level.levelgen.feature.Feature;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.jetbrains.annotations.VisibleForTesting;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.spigotmc.SpigotConfig;
|
||||
import org.spigotmc.SpigotWorldConfig;
|
||||
import org.spongepowered.configurate.BasicConfigurationNode;
|
||||
import org.spongepowered.configurate.ConfigurateException;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.ConfigurationOptions;
|
||||
import org.spongepowered.configurate.NodePath;
|
||||
import org.spongepowered.configurate.objectmapping.FieldDiscoverer;
|
||||
import org.spongepowered.configurate.objectmapping.ObjectMapper;
|
||||
import org.spongepowered.configurate.objectmapping.meta.NodeResolver;
|
||||
import org.spongepowered.configurate.serialize.SerializationException;
|
||||
import org.spongepowered.configurate.transformation.ConfigurationTransformation;
|
||||
import org.spongepowered.configurate.transformation.TransformAction;
|
||||
import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
|
||||
import org.spongepowered.configurate.util.NamingSchemes;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static io.leangen.geantyref.GenericTypeReflector.erase;
|
||||
|
||||
@SuppressWarnings("Convert2Diamond")
|
||||
@@ -95,7 +95,6 @@ public class PaperConfigurations extends Configurations<GlobalConfiguration, Wor
|
||||
static final String WORLD_DEFAULTS_CONFIG_FILE_NAME = "paper-world-defaults.yml";
|
||||
static final String WORLD_CONFIG_FILE_NAME = "paper-world.yml";
|
||||
public static final String CONFIG_DIR = "config";
|
||||
private static final String BACKUP_DIR ="legacy-backup";
|
||||
|
||||
private static final String GLOBAL_HEADER = String.format("""
|
||||
This is the global configuration file for Paper.
|
||||
@@ -141,22 +140,7 @@ public class PaperConfigurations extends Configurations<GlobalConfiguration, Wor
|
||||
map.require(WORLD_KEY)
|
||||
);
|
||||
|
||||
private static final String MOVED_NOTICE = """
|
||||
The global and world default configuration files have moved to %s
|
||||
and the world-specific configuration file has been moved inside
|
||||
the respective world folder.
|
||||
|
||||
See https://docs.papermc.io/paper/configuration for more information.
|
||||
""";
|
||||
|
||||
@VisibleForTesting
|
||||
public static final Supplier<SpigotWorldConfig> SPIGOT_WORLD_DEFAULTS = Suppliers.memoize(() -> new SpigotWorldConfig(RandomStringUtils.randomAlphabetic(255), Key.key(RandomStringUtils.randomAlphabetic(255).toLowerCase(Locale.ROOT))) {
|
||||
@Override // override to ensure "verbose" is false
|
||||
public void init() {
|
||||
SpigotConfig.readConfig(SpigotWorldConfig.class, this);
|
||||
}
|
||||
});
|
||||
public static final ContextKey<Supplier<SpigotWorldConfig>> SPIGOT_WORLD_CONFIG_CONTEXT_KEY = new ContextKey<>(new TypeToken<Supplier<SpigotWorldConfig>>() {}, "spigot world config");
|
||||
|
||||
|
||||
public PaperConfigurations(final Path globalFolder) {
|
||||
@@ -186,6 +170,7 @@ public class PaperConfigurations extends Configurations<GlobalConfiguration, Wor
|
||||
.register(new ComponentSerializer())
|
||||
.register(IntOr.Default.SERIALIZER)
|
||||
.register(IntOr.Disabled.SERIALIZER)
|
||||
.register(LongOr.Default.SERIALIZER)
|
||||
.register(DoubleOr.Default.SERIALIZER)
|
||||
.register(DoubleOr.Disabled.SERIALIZER)
|
||||
.register(BooleanOrDefault.SERIALIZER)
|
||||
@@ -198,54 +183,67 @@ public class PaperConfigurations extends Configurations<GlobalConfiguration, Wor
|
||||
|
||||
@Override
|
||||
protected ObjectMapper.Factory.Builder createGlobalObjectMapperFactoryBuilder() {
|
||||
return defaultGlobalFactoryBuilder(super.createGlobalObjectMapperFactoryBuilder());
|
||||
// populate the instance the early load created, so it isn't replaced
|
||||
return globalObjectMapperFactoryBuilder(super.createGlobalObjectMapperFactoryBuilder(), PaperConfigurationsInitializer.earlyGlobalConfig());
|
||||
}
|
||||
|
||||
private static ObjectMapper.Factory.Builder defaultGlobalFactoryBuilder(ObjectMapper.Factory.Builder builder) {
|
||||
return builder.addDiscoverer(InnerClassFieldDiscoverer.globalConfig(defaultFieldProcessors()));
|
||||
private static ObjectMapper.Factory.Builder globalObjectMapperFactoryBuilder(final ObjectMapper.Factory.Builder builder, final GlobalConfiguration instance) {
|
||||
return builder.addDiscoverer(InnerClassFieldDiscoverer.globalConfig(instance, defaultFieldProcessors()));
|
||||
}
|
||||
|
||||
private static ObjectMapper.Factory.Builder earlyGlobalObjectMapperFactoryBuilder(final GlobalConfiguration instance) {
|
||||
// not based on the default builder, it always adds the @PostProcess post-processor
|
||||
return addDefaultConstraints(ObjectMapper.emptyFactoryBuilder())
|
||||
.defaultNamingScheme(NamingSchemes.LOWER_CASE_DASHED)
|
||||
.addNodeResolver(NodeResolver.nodeKey())
|
||||
.addNodeResolver(NodeResolver.keyFromSetting())
|
||||
.addNodeResolver(NodeResolver.nodeFromParent())
|
||||
.addDiscoverer(FieldDiscoverer.emptyConstructorObject())
|
||||
.addDiscoverer(FieldDiscoverer.record())
|
||||
.addDiscoverer(InnerClassFieldDiscoverer.earlyGlobalConfig(instance, defaultFieldProcessors())); // added last, so it's checked first
|
||||
}
|
||||
|
||||
@Override
|
||||
protected YamlConfigurationLoader.Builder createGlobalLoaderBuilder(RegistryAccess registryAccess) {
|
||||
return super.createGlobalLoaderBuilder(registryAccess)
|
||||
.defaultOptions((options) -> defaultGlobalOptions(registryAccess, options));
|
||||
protected YamlConfigurationLoader.Builder createGlobalLoaderBuilder(final ContextMap contextMap) {
|
||||
final RegistryAccess access = contextMap.require(REGISTRY_ACCESS);
|
||||
return super.createGlobalLoaderBuilder(contextMap)
|
||||
.defaultOptions((options) -> defaultGlobalOptions(options, access));
|
||||
}
|
||||
|
||||
private static ConfigurationOptions defaultGlobalOptions(RegistryAccess registryAccess, ConfigurationOptions options) {
|
||||
private static ConfigurationOptions defaultGlobalOptions(ConfigurationOptions options, RegistryAccess registryAccess) {
|
||||
return options
|
||||
.header(GLOBAL_HEADER)
|
||||
.serializers(builder -> builder
|
||||
.register(new ServerboundPacketClassSerializer())
|
||||
.register(new RegistryValueSerializer<>(new TypeToken<DataComponentType<?>>() {}, registryAccess, Registries.DATA_COMPONENT_TYPE, false))
|
||||
// stats.forced-custom-stat-values
|
||||
.register(new TypeToken<Object2IntMap<?>>() {}, new FastutilMapSerializer.SomethingToPrimitive<Object2IntMap<?>>(Object2IntOpenHashMap::new, Integer.TYPE))
|
||||
.register(new RegistryHolderSerializer<>(new TypeToken<Identifier>() {}, registryAccess, Registries.CUSTOM_STAT, false))
|
||||
// attributes.overrides
|
||||
.register(new RegistryHolderSerializer<>(new TypeToken<Attribute>() {}, registryAccess, Registries.ATTRIBUTE, false))
|
||||
// spawning.spawn-limits / spawning.ticks-per-spawn
|
||||
.register(new TypeToken<Reference2IntMap<?>>() {}, new FastutilMapSerializer.SomethingToPrimitive<Reference2IntMap<?>>(Reference2IntOpenHashMap::new, Integer.TYPE))
|
||||
.register(StringRepresentableSerializer::isValidFor, new StringRepresentableSerializer())
|
||||
.register(new EnumValueSerializer())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalConfiguration initializeGlobalConfiguration(final RegistryAccess registryAccess) throws ConfigurateException {
|
||||
GlobalConfiguration configuration = super.initializeGlobalConfiguration(registryAccess);
|
||||
public GlobalConfiguration initializeGlobalConfiguration(final DedicatedServer server) throws ConfigurateException {
|
||||
GlobalConfiguration configuration = super.initializeGlobalConfiguration(server);
|
||||
GlobalConfiguration.set(configuration);
|
||||
return configuration;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ContextMap.Builder createDefaultContextMap(final RegistryAccess registryAccess) {
|
||||
return super.createDefaultContextMap(registryAccess)
|
||||
.put(SPIGOT_WORLD_CONFIG_CONTEXT_KEY, SPIGOT_WORLD_DEFAULTS);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ObjectMapper.Factory.Builder createWorldObjectMapperFactoryBuilder(final ContextMap contextMap) {
|
||||
return super.createWorldObjectMapperFactoryBuilder(contextMap)
|
||||
.addNodeResolver(new RequiresSpigotInitialization.Factory(contextMap.require(SPIGOT_WORLD_CONFIG_CONTEXT_KEY).get()))
|
||||
.addNodeResolver(new NestedSetting.Factory())
|
||||
.addDiscoverer(InnerClassFieldDiscoverer.worldConfig(createWorldConfigInstance(contextMap), defaultFieldProcessors()));
|
||||
}
|
||||
|
||||
private static WorldConfiguration createWorldConfigInstance(ContextMap contextMap) {
|
||||
return new WorldConfiguration(
|
||||
contextMap.require(PaperConfigurations.SPIGOT_WORLD_CONFIG_CONTEXT_KEY).get(),
|
||||
contextMap.require(Configurations.WORLD_KEY)
|
||||
);
|
||||
return new WorldConfiguration(contextMap.require(Configurations.WORLD_KEY));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -262,7 +260,7 @@ public class PaperConfigurations extends Configurations<GlobalConfiguration, Wor
|
||||
.register(DespawnRange.class, DespawnRange.SERIALIZER)
|
||||
.register(StringRepresentableSerializer::isValidFor, new StringRepresentableSerializer())
|
||||
.register(EngineMode.SERIALIZER)
|
||||
.register(FallbackValueSerializer.create(contextMap.require(SPIGOT_WORLD_CONFIG_CONTEXT_KEY).get(), MinecraftServer::getServer))
|
||||
.register(FallbackValueSerializer.create(createWorldConfigInstance(contextMap), MinecraftServer::getServer))
|
||||
.register(new RegistryValueSerializer<>(new TypeToken<EntityType<?>>() {}, access, Registries.ENTITY_TYPE, true))
|
||||
.register(new RegistryValueSerializer<>(Item.class, access, Registries.ITEM, true))
|
||||
.register(new RegistryValueSerializer<>(Block.class, access, Registries.BLOCK, true))
|
||||
@@ -282,12 +280,48 @@ public class PaperConfigurations extends Configurations<GlobalConfiguration, Wor
|
||||
final ConfigurationTransformation.VersionedBuilder versionedBuilder = Transformations.versionedBuilder();
|
||||
V29_ZeroWorldHeight.apply(versionedBuilder);
|
||||
V30_RenameFilterNbtFromSpawnEgg.apply(versionedBuilder);
|
||||
V32_WorldConfigCleanup.apply(versionedBuilder);
|
||||
// ADD FUTURE VERSIONED TRANSFORMS TO versionedBuilder HERE
|
||||
versionedBuilder.build().apply(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyGlobalConfigTransformations(ConfigurationNode node) throws ConfigurateException {
|
||||
protected void applyGlobalConfigTransformations(final ContextMap contextMap, final ConfigurationNode node) throws ConfigurateException {
|
||||
applyGlobalTransformations(contextMap, node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies removals and versioned transformations for the global config.
|
||||
*
|
||||
* @param contextMap context for the transformations, which must contain everything each of them requires
|
||||
* @param node configuration node
|
||||
*/
|
||||
static void applyGlobalTransformations(final ContextMap contextMap, final ConfigurationNode node) throws ConfigurateException {
|
||||
applyGlobalTransformations(contextMap, node, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the global transformations that can run before the server exists, for reading {@link LoadEarly} fields.
|
||||
* Every transformation requiring context that isn't available yet is skipped, so the node must never be saved.
|
||||
*
|
||||
* @param node configuration node
|
||||
* @see PaperConfigurationsInitializer#loadEarlyGlobalConfig(Path)
|
||||
*/
|
||||
static void applyEarlyGlobalTransformations(final ConfigurationNode node) throws ConfigurateException {
|
||||
applyGlobalTransformations(ContextMap.builder().build(), node, true);
|
||||
}
|
||||
|
||||
private static List<ContextualTransformation> versionedGlobalTransformations() {
|
||||
return List.of(
|
||||
V29_LogIPs.INSTANCE,
|
||||
V30_PacketIds.INSTANCE,
|
||||
V31_AllowNetherPropertiesToConfig.INSTANCE,
|
||||
V32_SpigotConfigCleanup.INSTANCE
|
||||
// ADD FUTURE VERSIONED TRANSFORMS HERE
|
||||
);
|
||||
}
|
||||
|
||||
private static void applyGlobalTransformations(final ContextMap contextMap, final ConfigurationNode node, final boolean skipUnavailable) throws ConfigurateException {
|
||||
ConfigurationTransformation.Builder builder = ConfigurationTransformation.builder();
|
||||
for (NodePath path : RemovedConfigurations.REMOVED_GLOBAL_PATHS) {
|
||||
builder.addAction(path, TransformAction.remove());
|
||||
@@ -295,22 +329,38 @@ public class PaperConfigurations extends Configurations<GlobalConfiguration, Wor
|
||||
builder.build().apply(node);
|
||||
|
||||
final ConfigurationTransformation.VersionedBuilder versionedBuilder = Transformations.versionedBuilder();
|
||||
V29_LogIPs.apply(versionedBuilder);
|
||||
V30_PacketIds.apply(versionedBuilder);
|
||||
V31_AllowNetherPropertiesToConfig.apply(versionedBuilder);
|
||||
// ADD FUTURE VERSIONED TRANSFORMS TO versionedBuilder HERE
|
||||
for (final ContextualTransformation transformation : versionedGlobalTransformations()) {
|
||||
final List<String> missing = transformation.requiredContext().stream()
|
||||
.filter(key -> !contextMap.has(key))
|
||||
.map(ContextKey::name)
|
||||
.toList();
|
||||
if (missing.isEmpty()) {
|
||||
versionedBuilder.addVersion(transformation.version(), transformation.create(contextMap));
|
||||
} else if (!skipUnavailable) {
|
||||
// the version is bumped and saved regardless, so the skipped migration would be lost for good
|
||||
throw new IllegalStateException("Global config transformation to version " + transformation.version() + " requires " + String.join(", ", missing));
|
||||
}
|
||||
}
|
||||
versionedBuilder.build().apply(node);
|
||||
}
|
||||
|
||||
private static final List<Transformations.DefaultsAware> DEFAULT_AWARE_TRANSFORMATIONS = List.of(
|
||||
FeatureSeedsGeneration::apply
|
||||
);
|
||||
/**
|
||||
* Options for loading the global config file before {@code Bootstrap} has run and {@code RegistryAccess} exists.
|
||||
* Only {@link LoadEarly} fields are deserialized, into the given instance, and no post-processors are run.
|
||||
*
|
||||
* @param options base options
|
||||
* @param instance global config instance to populate
|
||||
* @return early global config options
|
||||
*/
|
||||
static ConfigurationOptions earlyGlobalOptions(final ConfigurationOptions options, final GlobalConfiguration instance) {
|
||||
return applyObjectMapperFactory(defaultOptions(options), PaperConfigurations::isConfigPart, earlyGlobalObjectMapperFactoryBuilder(instance).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyDefaultsAwareWorldConfigTransformations(final ContextMap contextMap, final ConfigurationNode worldNode, final ConfigurationNode defaultsNode) throws ConfigurateException {
|
||||
final ConfigurationTransformation.Builder builder = ConfigurationTransformation.builder();
|
||||
// ADD FUTURE TRANSFORMS HERE (these transforms run after the defaults have been merged into the node)
|
||||
DEFAULT_AWARE_TRANSFORMATIONS.forEach(transform -> transform.apply(builder, contextMap, defaultsNode));
|
||||
FeatureSeedsGeneration.apply(builder, contextMap, defaultsNode);
|
||||
builder.build().apply(worldNode);
|
||||
}
|
||||
|
||||
@@ -326,17 +376,21 @@ public class PaperConfigurations extends Configurations<GlobalConfiguration, Wor
|
||||
|
||||
@Override
|
||||
protected boolean isConfigType(final Type type) {
|
||||
return isConfigPart(type);
|
||||
}
|
||||
|
||||
private static boolean isConfigPart(final Type type) {
|
||||
return ConfigurationPart.class.isAssignableFrom(erase(type));
|
||||
}
|
||||
|
||||
public void reloadConfigs(MinecraftServer server) {
|
||||
public void reloadConfigs(final DedicatedServer server) {
|
||||
try {
|
||||
this.initializeGlobalConfiguration(server.registryAccess(), reloader(this.globalConfigClass, GlobalConfiguration.get()));
|
||||
this.initializeGlobalConfiguration(this.createGlobalContextMap(server).build(), reloader(this.globalConfigClass, GlobalConfiguration.get()));
|
||||
this.initializeWorldDefaultsConfiguration(server.registryAccess());
|
||||
for (ServerLevel level : server.getAllLevels()) {
|
||||
for (final ServerLevel level : server.getAllLevels()) {
|
||||
this.createWorldConfig(createWorldContextMap(level), reloader(this.worldConfigClass, level.paperConfig()));
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
} catch (final Exception ex) {
|
||||
throw new RuntimeException("Could not reload paper configuration files", ex);
|
||||
}
|
||||
}
|
||||
@@ -348,51 +402,19 @@ public class PaperConfigurations extends Configurations<GlobalConfiguration, Wor
|
||||
}
|
||||
|
||||
private static ContextMap createWorldContextMap(ServerLevel level) {
|
||||
return createWorldContextMap(level.getServer().storageSource.getDimensionPath(level.dimension()), level.dimension().identifier(), level.spigotConfig, level.registryAccess(), level.getGameRules());
|
||||
return createWorldContextMap(level.getServer().storageSource.getDimensionPath(level.dimension()), level.dimension().identifier(), level.registryAccess(), level.getGameRules());
|
||||
}
|
||||
|
||||
public static ContextMap createWorldContextMap(final Path dir, final Identifier worldKey, final SpigotWorldConfig spigotConfig, final RegistryAccess registryAccess, final GameRules gameRules) {
|
||||
public static ContextMap createWorldContextMap(final Path dir, final Identifier worldKey, final RegistryAccess registryAccess, final GameRules gameRules) {
|
||||
return ContextMap.builder()
|
||||
.put(WORLD_DIRECTORY, dir)
|
||||
.put(WORLD_KEY, worldKey)
|
||||
.put(SPIGOT_WORLD_CONFIG_CONTEXT_KEY, Suppliers.ofInstance(spigotConfig))
|
||||
.put(REGISTRY_ACCESS, registryAccess)
|
||||
.put(GAME_RULES, gameRules)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static PaperConfigurations setup(final Path legacyConfig, final Path configDir, final Path worldFolder, final File spigotConfig) throws Exception {
|
||||
final Path legacy = Files.isSymbolicLink(legacyConfig) ? Files.readSymbolicLink(legacyConfig) : legacyConfig;
|
||||
if (needsConverting(legacyConfig)) {
|
||||
final String legacyFileName = legacyConfig.getFileName().toString();
|
||||
try {
|
||||
if (Files.exists(configDir) && !Files.isDirectory(configDir)) {
|
||||
throw new RuntimeException("Paper needs to create a '" + configDir.toAbsolutePath() + "' folder. You already have a non-directory named '" + configDir.toAbsolutePath() + "'. Please remove it and restart the server.");
|
||||
}
|
||||
final Path backupDir = configDir.resolve(BACKUP_DIR);
|
||||
if (Files.exists(backupDir) && !Files.isDirectory(backupDir)) {
|
||||
throw new RuntimeException("Paper needs to create a '" + BACKUP_DIR + "' directory in the '" + configDir.toAbsolutePath() + "' folder. You already have a non-directory named '" + BACKUP_DIR + "'. Please remove it and restart the server.");
|
||||
}
|
||||
createDirectoriesSymlinkAware(backupDir);
|
||||
final String backupFileName = legacyFileName + ".old";
|
||||
final Path legacyConfigBackup = backupDir.resolve(backupFileName);
|
||||
if (Files.exists(legacyConfigBackup) && !Files.isRegularFile(legacyConfigBackup)) {
|
||||
throw new RuntimeException("Paper needs to create a '" + backupFileName + "' file in the '" + backupDir.toAbsolutePath() + "' folder. You already have a non-file named '" + backupFileName + "'. Please remove it and restart the server.");
|
||||
}
|
||||
Files.move(legacyConfig.toRealPath(), legacyConfigBackup, StandardCopyOption.REPLACE_EXISTING); // make backup
|
||||
if (Files.isSymbolicLink(legacyConfig)) {
|
||||
Files.delete(legacyConfig);
|
||||
}
|
||||
final Path replacementFile = legacy.resolveSibling(legacyFileName + "-README.txt");
|
||||
if (Files.notExists(replacementFile)) {
|
||||
Files.createFile(replacementFile);
|
||||
Files.writeString(replacementFile, String.format(MOVED_NOTICE, configDir.toAbsolutePath()));
|
||||
}
|
||||
convert(legacyConfigBackup, configDir, worldFolder, spigotConfig);
|
||||
} catch (final IOException ex) {
|
||||
throw new RuntimeException("Could not convert '" + legacyFileName + "' to the new configuration format", ex);
|
||||
}
|
||||
}
|
||||
public static PaperConfigurations setup(final Path configDir) throws Exception {
|
||||
try {
|
||||
createDirectoriesSymlinkAware(configDir);
|
||||
return new PaperConfigurations(configDir);
|
||||
@@ -401,88 +423,41 @@ public class PaperConfigurations extends Configurations<GlobalConfiguration, Wor
|
||||
}
|
||||
}
|
||||
|
||||
private static void convert(final Path legacyConfig, final Path configDir, final Path worldFolder, final File spigotConfig) throws Exception {
|
||||
createDirectoriesSymlinkAware(configDir);
|
||||
|
||||
final YamlConfigurationLoader legacyLoader = ConfigurationLoaders.naturallySortedWithoutHeader(legacyConfig);
|
||||
final YamlConfigurationLoader globalLoader = ConfigurationLoaders.naturallySortedWithoutHeader(configDir.resolve(GLOBAL_CONFIG_FILE_NAME));
|
||||
final YamlConfigurationLoader worldDefaultsLoader = ConfigurationLoaders.naturallySortedWithoutHeader(configDir.resolve(WORLD_DEFAULTS_CONFIG_FILE_NAME));
|
||||
|
||||
final ConfigurationNode legacy = legacyLoader.load();
|
||||
checkState(!legacy.virtual(), "can't be virtual");
|
||||
final int version = legacy.node(Configuration.LEGACY_CONFIG_VERSION_FIELD).getInt();
|
||||
|
||||
final ConfigurationNode legacyWorldSettings = legacy.node("world-settings").copy();
|
||||
checkState(!legacyWorldSettings.virtual(), "can't be virtual");
|
||||
legacy.removeChild("world-settings");
|
||||
|
||||
// Apply legacy transformations before settings flatten
|
||||
final YamlConfiguration spigotConfiguration = loadLegacyConfigFile(spigotConfig); // needs to change spigot config values in this transformation
|
||||
LegacyPaperConfig.transformation(spigotConfiguration).apply(legacy);
|
||||
spigotConfiguration.save(spigotConfig);
|
||||
legacy.mergeFrom(legacy.node("settings")); // flatten "settings" to root
|
||||
legacy.removeChild("settings");
|
||||
LegacyPaperConfig.toNewFormat().apply(legacy);
|
||||
globalLoader.save(legacy); // save converted node to new global location
|
||||
|
||||
final ConfigurationNode worldDefaults = legacyWorldSettings.node("default").copy();
|
||||
checkState(!worldDefaults.virtual());
|
||||
worldDefaults.node(Configuration.LEGACY_CONFIG_VERSION_FIELD).raw(version);
|
||||
legacyWorldSettings.removeChild("default");
|
||||
LegacyPaperWorldConfig.transformation().apply(worldDefaults);
|
||||
LegacyPaperWorldConfig.toNewFormat().apply(worldDefaults);
|
||||
worldDefaultsLoader.save(worldDefaults);
|
||||
|
||||
legacyWorldSettings.childrenMap().forEach((world, legacyWorldNode) -> {
|
||||
try {
|
||||
legacyWorldNode.node(Configuration.LEGACY_CONFIG_VERSION_FIELD).raw(version);
|
||||
LegacyPaperWorldConfig.transformation().apply(legacyWorldNode);
|
||||
LegacyPaperWorldConfig.toNewFormat().apply(legacyWorldNode);
|
||||
ConfigurationLoaders.naturallySortedWithoutHeader(worldFolder.resolve(world.toString()).resolve(WORLD_CONFIG_FILE_NAME)).save(legacyWorldNode); // save converted node to new location
|
||||
} catch (final ConfigurateException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static boolean needsConverting(final Path legacyConfig) {
|
||||
return Files.exists(legacyConfig) && Files.isRegularFile(legacyConfig);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public YamlConfiguration createLegacyObject(final MinecraftServer server) {
|
||||
YamlConfiguration global = YamlConfiguration.loadConfiguration(this.globalFolder.resolve(this.globalConfigFileName).toFile());
|
||||
ConfigurationSection worlds = global.createSection("__________WORLDS__________");
|
||||
final YamlConfiguration global = YamlConfiguration.loadConfiguration(this.globalFolder.resolve(this.globalConfigFileName).toFile());
|
||||
final ConfigurationSection worlds = global.createSection("__________WORLDS__________");
|
||||
worlds.set("__defaults__", YamlConfiguration.loadConfiguration(this.globalFolder.resolve(this.defaultWorldConfigFileName).toFile()));
|
||||
for (ServerLevel level : server.getAllLevels()) {
|
||||
worlds.set(level.getWorld().getName(), YamlConfiguration.loadConfiguration(getWorldConfigFile(level).toFile()));
|
||||
for (final ServerLevel level : server.getAllLevels()) {
|
||||
worlds.set(level.getWorld().getName(), YamlConfiguration.loadConfiguration(this.getWorldConfigFile(level).toFile()));
|
||||
}
|
||||
return global;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static YamlConfiguration loadLegacyConfigFile(File configFile) throws Exception {
|
||||
YamlConfiguration config = new YamlConfiguration();
|
||||
if (configFile.exists()) {
|
||||
try {
|
||||
config.load(configFile);
|
||||
} catch (Exception ex) {
|
||||
throw new Exception("Failed to load configuration file: " + configFile.getName(), ex);
|
||||
}
|
||||
}
|
||||
return config;
|
||||
|
||||
/**
|
||||
* Creates the global config with all defaults, the same way {@link #initializeGlobalConfiguration(DedicatedServer)} would.
|
||||
*
|
||||
* @param registryAccess registry access
|
||||
* @return default global config
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static GlobalConfiguration createGlobalForTesting(final RegistryAccess registryAccess) throws SerializationException {
|
||||
final GlobalConfiguration instance = new GlobalConfiguration();
|
||||
final ObjectMapper.Factory factory = globalObjectMapperFactoryBuilder(defaultObjectMapperFactoryBuilder(), instance).build();
|
||||
final ConfigurationOptions options = applyObjectMapperFactory(defaultGlobalOptions(defaultOptions(ConfigurationOptions.defaults()), registryAccess), PaperConfigurations::isConfigPart, factory);
|
||||
BasicConfigurationNode.root(options).require(GlobalConfiguration.class); // populates the instance passed to the factory
|
||||
return instance;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static ConfigurationNode createForTesting(RegistryAccess registryAccess) {
|
||||
ObjectMapper.Factory factory = defaultGlobalFactoryBuilder(ObjectMapper.factoryBuilder()).build();
|
||||
ConfigurationOptions options = defaultGlobalOptions(registryAccess, defaultOptions(ConfigurationOptions.defaults()))
|
||||
.serializers(builder -> builder.register(type -> ConfigurationPart.class.isAssignableFrom(erase(type)), factory.asTypeSerializer()));
|
||||
return BasicConfigurationNode.root(options);
|
||||
public static WorldConfiguration createWorldDefaultsForTesting() {
|
||||
return new WorldConfiguration(Configurations.WORLD_DEFAULTS_KEY);
|
||||
}
|
||||
|
||||
// Symlinks are not correctly checked in createDirectories
|
||||
static void createDirectoriesSymlinkAware(Path path) throws IOException {
|
||||
static void createDirectoriesSymlinkAware(final Path path) throws IOException {
|
||||
if (!Files.isDirectory(path)) {
|
||||
Files.createDirectories(path);
|
||||
}
|
||||
|
||||
+396
@@ -0,0 +1,396 @@
|
||||
package io.papermc.paper.configuration;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import io.papermc.paper.configuration.mapping.LoadEarly;
|
||||
import io.papermc.paper.configuration.transformation.global.LegacyBukkitConfig;
|
||||
import io.papermc.paper.configuration.transformation.global.LegacyPaperConfig;
|
||||
import io.papermc.paper.configuration.transformation.global.LegacySpigotConfig;
|
||||
import io.papermc.paper.configuration.transformation.world.LegacyPaperWorldConfig;
|
||||
import io.papermc.paper.configuration.transformation.world.LegacySpigotWorldConfig;
|
||||
import io.papermc.paper.configuration.yaml.YamlConfigurationLoader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.Set;
|
||||
import joptsimple.OptionSet;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.spongepowered.configurate.BasicConfigurationNode;
|
||||
import org.spongepowered.configurate.ConfigurateException;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.ConfigurationOptions;
|
||||
import org.spongepowered.configurate.serialize.SerializationException;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static io.papermc.paper.configuration.PaperConfigurations.GLOBAL_CONFIG_FILE_NAME;
|
||||
import static io.papermc.paper.configuration.PaperConfigurations.WORLD_CONFIG_FILE_NAME;
|
||||
import static io.papermc.paper.configuration.PaperConfigurations.WORLD_DEFAULTS_CONFIG_FILE_NAME;
|
||||
import static io.papermc.paper.configuration.PaperConfigurations.createDirectoriesSymlinkAware;
|
||||
|
||||
public final class PaperConfigurationsInitializer {
|
||||
|
||||
private static final Logger LOGGER = LogUtils.getClassLogger();
|
||||
|
||||
private static final String BACKUP_DIR ="legacy-backup";
|
||||
|
||||
private static final String MOVED_NOTICE = """
|
||||
The global and world default configuration files have moved to %s
|
||||
and the world-specific configuration file has been moved inside
|
||||
the respective world folder.
|
||||
|
||||
See https://docs.papermc.io/paper/configuration for more information.
|
||||
""";
|
||||
|
||||
// Also read from contexts that never actually boot a server
|
||||
private static @Nullable GlobalConfiguration earlyGlobalConfig;
|
||||
|
||||
/**
|
||||
* Returns the global config to read {@link LoadEarly} fields from, at any point during startup.
|
||||
* <p>
|
||||
* Until {@code DedicatedServer#initServer} fully loads the {@link GlobalConfiguration}, this returns the
|
||||
* instance created by {@link #load(OptionSet)}, which only has its {@link LoadEarly} fields populated.
|
||||
* Every other field holds its default or is {@code null}. The full load then populates this same instance.
|
||||
*
|
||||
* @return global config with at least its early fields loaded
|
||||
*/
|
||||
public static GlobalConfiguration earlyGlobalConfig() {
|
||||
final GlobalConfiguration global = GlobalConfiguration.get();
|
||||
//noinspection ConstantValue
|
||||
if (global != null) {
|
||||
return global;
|
||||
}
|
||||
if (earlyGlobalConfig == null) {
|
||||
try {
|
||||
earlyGlobalConfig = loadEarlyGlobalConfig(null);
|
||||
} catch (final ConfigurateException ex) {
|
||||
throw new RuntimeException("Could not create the default early global config", ex);
|
||||
}
|
||||
}
|
||||
return earlyGlobalConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the early configuration load for the {@link #earlyGlobalConfig()}.
|
||||
*
|
||||
* @param options startup options
|
||||
*/
|
||||
public static void load(final OptionSet options) throws IOException {
|
||||
final Path configDirPath = ((File) options.valueOf("paper-settings-directory")).toPath();
|
||||
final GlobalConfiguration configuration = loadEarlyGlobalConfig(configDirPath.resolve(GLOBAL_CONFIG_FILE_NAME));
|
||||
|
||||
// Migration only comes later, so we need to manually look into legacy files that haven't been converted yet
|
||||
final Path legacyBukkitConfigPath = ((File) options.valueOf("bukkit-settings")).toPath();
|
||||
if (needsConverting(legacyBukkitConfigPath)) {
|
||||
final YamlConfiguration bukkit = loadLegacyConfigFile(legacyBukkitConfigPath);
|
||||
configuration.misc.worldContainer = bukkit.getString("settings.world-container", configuration.misc.worldContainer);
|
||||
configuration.misc.updateFolder = bukkit.getString("settings.update-folder", configuration.misc.updateFolder);
|
||||
}
|
||||
|
||||
final Path legacySpigotConfigPath = ((File) options.valueOf("spigot-settings")).toPath();
|
||||
if (needsConverting(legacySpigotConfigPath)) {
|
||||
final YamlConfiguration spigot = loadLegacyConfigFile(legacySpigotConfigPath);
|
||||
if (spigot.contains("advancements.disabled")) {
|
||||
configuration.advancements.disabled = Set.copyOf(spigot.getStringList("advancements.disabled"));
|
||||
}
|
||||
}
|
||||
|
||||
earlyGlobalConfig = configuration;
|
||||
}
|
||||
|
||||
static GlobalConfiguration loadEarlyGlobalConfig(final @Nullable Path globalConfigFile) throws ConfigurateException {
|
||||
final GlobalConfiguration configuration = new GlobalConfiguration();
|
||||
final ConfigurationNode node;
|
||||
if (globalConfigFile == null || Files.notExists(globalConfigFile)) {
|
||||
node = BasicConfigurationNode.root(PaperConfigurations.earlyGlobalOptions(ConfigurationOptions.defaults(), configuration));
|
||||
} else {
|
||||
node = ConfigurationLoaders.naturallySorted()
|
||||
.defaultOptions(options -> PaperConfigurations.earlyGlobalOptions(options, configuration))
|
||||
.path(globalConfigFile)
|
||||
.build()
|
||||
.load();
|
||||
final ConfigurationNode version = node.node(Configuration.VERSION_FIELD);
|
||||
if (version.virtual()) { // See Configurations#verifyGlobalConfigVersion
|
||||
version.raw(GlobalConfiguration.CURRENT_VERSION);
|
||||
}
|
||||
|
||||
PaperConfigurations.applyEarlyGlobalTransformations(node);
|
||||
}
|
||||
|
||||
node.require(GlobalConfiguration.class); // populates the instance passed to the options
|
||||
return configuration;
|
||||
}
|
||||
|
||||
public static PaperConfigurations create(final OptionSet options) throws Exception {
|
||||
final Path legacyBukkitConfigPath = ((File) options.valueOf("bukkit-settings")).toPath();
|
||||
final Path legacySpigotConfigPath = ((File) options.valueOf("spigot-settings")).toPath();
|
||||
final Path legacyPaperConfig = ((File) options.valueOf("paper-settings")).toPath();
|
||||
final Path configDirPath = ((File) options.valueOf("paper-settings-directory")).toPath();
|
||||
final Path worldFolder = options.has("universe") ? ((File) options.valueOf("universe")).toPath() : Path.of(earlyGlobalConfig().misc.worldContainer); // the launch argument always wins
|
||||
convertLegacy(legacyPaperConfig, configDirPath, worldFolder, legacySpigotConfigPath, legacyBukkitConfigPath, worldFolder.resolve(levelName(options)));
|
||||
return PaperConfigurations.setup(configDirPath);
|
||||
}
|
||||
|
||||
private static void convertLegacy(final Path legacyPaperConfig, final Path configDir, final Path worldFolder, final Path legacySpigotConfig, final Path legacyBukkitConfig, final Path levelFolder) throws Exception {
|
||||
if (needsConverting(legacyPaperConfig)) {
|
||||
final String legacyFileName = legacyPaperConfig.getFileName().toString();
|
||||
try {
|
||||
final Path legacyConfigBackup = makeConfigFileBackup(legacyPaperConfig, configDir);
|
||||
convertLegacyPaperConfig(legacyConfigBackup, configDir, worldFolder, legacySpigotConfig);
|
||||
} catch (final IOException ex) {
|
||||
throw new RuntimeException("Could not convert '" + legacyFileName + "' to the new configuration format", ex);
|
||||
}
|
||||
}
|
||||
if (needsConverting(legacyBukkitConfig)) {
|
||||
try {
|
||||
final Path legacyConfigBackup = makeConfigFileBackup(legacyBukkitConfig, configDir);
|
||||
convertLegacyBukkitConfig(legacyConfigBackup, configDir);
|
||||
} catch (final IOException ex) {
|
||||
throw new RuntimeException("Could not convert '" + legacyBukkitConfig.getFileName() + "' to the new configuration format", ex);
|
||||
}
|
||||
}
|
||||
if (needsConverting(legacySpigotConfig)) {
|
||||
final String legacyFileName = legacySpigotConfig.getFileName().toString();
|
||||
try {
|
||||
final Path legacyConfigBackup = makeConfigFileBackup(legacySpigotConfig, configDir);
|
||||
convertLegacySpigotConfig(legacyConfigBackup, configDir, levelFolder);
|
||||
} catch (final IOException ex) {
|
||||
throw new RuntimeException("Could not convert '" + legacyFileName + "' to the new configuration format", ex);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static Path makeConfigFileBackup(final Path legacyConfigPath, final Path configDir) throws IOException {
|
||||
if (Files.exists(configDir) && !Files.isDirectory(configDir)) {
|
||||
throw new RuntimeException("Paper needs to create a '" + configDir.toAbsolutePath() + "' folder. You already have a non-directory named '" + configDir.toAbsolutePath() + "'. Please remove it and restart the server.");
|
||||
}
|
||||
|
||||
final Path backupDir = configDir.resolve(BACKUP_DIR);
|
||||
if (Files.exists(backupDir) && !Files.isDirectory(backupDir)) {
|
||||
throw new RuntimeException("Paper needs to create a '" + BACKUP_DIR + "' directory in the '" + configDir.toAbsolutePath() + "' folder. You already have a non-directory named '" + BACKUP_DIR + "'. Please remove it and restart the server.");
|
||||
}
|
||||
|
||||
createDirectoriesSymlinkAware(backupDir);
|
||||
|
||||
final Path legacyFileName = legacyConfigPath.getFileName();
|
||||
final String backupFileName = legacyFileName + ".old";
|
||||
final Path legacyConfigBackup = backupDir.resolve(backupFileName);
|
||||
if (Files.exists(legacyConfigBackup) && !Files.isRegularFile(legacyConfigBackup)) {
|
||||
throw new RuntimeException("Paper needs to create a '" + backupFileName + "' file in the '" + backupDir.toAbsolutePath() + "' folder. You already have a non-file named '" + backupFileName + "'. Please remove it and restart the server.");
|
||||
}
|
||||
// resolve the symlink before the move, the link is gone by the time the README is placed
|
||||
final boolean symlink = Files.isSymbolicLink(legacyConfigPath);
|
||||
final Path realLegacyPath = symlink ? Files.readSymbolicLink(legacyConfigPath) : legacyConfigPath;
|
||||
Files.move(legacyConfigPath.toRealPath(), legacyConfigBackup, StandardCopyOption.REPLACE_EXISTING); // make backup
|
||||
if (symlink) {
|
||||
Files.delete(legacyConfigPath);
|
||||
}
|
||||
final Path replacementFile = realLegacyPath.resolveSibling(legacyFileName + "-README.txt");
|
||||
if (Files.notExists(replacementFile)) {
|
||||
Files.createFile(replacementFile);
|
||||
Files.writeString(replacementFile, String.format(MOVED_NOTICE, configDir.toAbsolutePath()));
|
||||
}
|
||||
return legacyConfigBackup;
|
||||
}
|
||||
|
||||
private static void convertLegacyBukkitConfig(final Path legacyConfig, final Path configDir) throws Exception {
|
||||
createDirectoriesSymlinkAware(configDir);
|
||||
|
||||
final YamlConfigurationLoader globalLoader = ConfigurationLoaders.naturallySortedWithoutHeader(configDir.resolve(GLOBAL_CONFIG_FILE_NAME));
|
||||
final ConfigurationNode global = globalLoader.load();
|
||||
final ConfigurationNode legacy = ConfigurationLoaders.naturallySortedWithoutHeader(legacyConfig).load();
|
||||
|
||||
// spawn limits, spawn intervals and the save interval are world settings now
|
||||
final ConfigurationNode worldBound = legacy.copy();
|
||||
LegacyBukkitConfig.migrateWorldDefaults(worldBound);
|
||||
if (!worldBound.empty()) {
|
||||
final YamlConfigurationLoader worldDefaultsLoader = ConfigurationLoaders.naturallySortedWithoutHeader(configDir.resolve(WORLD_DEFAULTS_CONFIG_FILE_NAME));
|
||||
final ConfigurationNode worldDefaults = worldDefaultsLoader.load();
|
||||
overwriteDeferredValues(worldBound, worldDefaults);
|
||||
worldDefaultsLoader.save(worldDefaults);
|
||||
}
|
||||
|
||||
LegacyBukkitConfig.migrate(legacy);
|
||||
global.mergeFrom(legacy);
|
||||
globalLoader.save(global);
|
||||
LOGGER.info("Converted bukkit.yml into {}", GLOBAL_CONFIG_FILE_NAME);
|
||||
}
|
||||
|
||||
private static void convertLegacyPaperConfig(final Path legacyConfig, final Path configDir, final Path worldFolder, final Path spigotConfig) throws Exception {
|
||||
createDirectoriesSymlinkAware(configDir);
|
||||
|
||||
final YamlConfigurationLoader legacyLoader = ConfigurationLoaders.naturallySortedWithoutHeader(legacyConfig);
|
||||
final YamlConfigurationLoader globalLoader = ConfigurationLoaders.naturallySortedWithoutHeader(configDir.resolve(GLOBAL_CONFIG_FILE_NAME));
|
||||
final YamlConfigurationLoader worldDefaultsLoader = ConfigurationLoaders.naturallySortedWithoutHeader(configDir.resolve(WORLD_DEFAULTS_CONFIG_FILE_NAME));
|
||||
|
||||
final ConfigurationNode legacy = legacyLoader.load();
|
||||
checkState(!legacy.virtual(), "can't be virtual");
|
||||
final int version = legacy.node(Configuration.LEGACY_CONFIG_VERSION_FIELD).getInt();
|
||||
|
||||
final ConfigurationNode legacyWorldSettings = legacy.node("world-settings").copy();
|
||||
checkState(!legacyWorldSettings.virtual(), "can't be virtual");
|
||||
legacy.removeChild("world-settings");
|
||||
|
||||
// Apply legacy transformations before settings flatten
|
||||
final YamlConfiguration spigotConfiguration = loadLegacyConfigFile(spigotConfig); // needs to change spigot config values in this transformation
|
||||
LegacyPaperConfig.transformation(spigotConfiguration).apply(legacy);
|
||||
spigotConfiguration.save(spigotConfig.toFile());
|
||||
legacy.mergeFrom(legacy.node("settings")); // flatten "settings" to root
|
||||
legacy.removeChild("settings");
|
||||
LegacyPaperConfig.toNewFormat().apply(legacy);
|
||||
globalLoader.save(legacy); // save converted node to new global location
|
||||
|
||||
final ConfigurationNode worldDefaults = legacyWorldSettings.node("default").copy();
|
||||
checkState(!worldDefaults.virtual());
|
||||
worldDefaults.node(Configuration.LEGACY_CONFIG_VERSION_FIELD).raw(version);
|
||||
legacyWorldSettings.removeChild("default");
|
||||
LegacyPaperWorldConfig.transformation().apply(worldDefaults);
|
||||
LegacyPaperWorldConfig.toNewFormat().apply(worldDefaults);
|
||||
worldDefaultsLoader.save(worldDefaults);
|
||||
|
||||
legacyWorldSettings.childrenMap().forEach((world, legacyWorldNode) -> {
|
||||
try {
|
||||
legacyWorldNode.node(Configuration.LEGACY_CONFIG_VERSION_FIELD).raw(version);
|
||||
LegacyPaperWorldConfig.transformation().apply(legacyWorldNode);
|
||||
LegacyPaperWorldConfig.toNewFormat().apply(legacyWorldNode);
|
||||
ConfigurationLoaders.naturallySortedWithoutHeader(worldFolder.resolve(world.toString()).resolve(WORLD_CONFIG_FILE_NAME)).save(legacyWorldNode); // save converted node to new location
|
||||
} catch (final ConfigurateException ex) {
|
||||
LOGGER.error("Failed to convert legacy world config", ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void convertLegacySpigotConfig(final Path legacyConfig, final Path configDir, final Path levelFolder) throws Exception {
|
||||
createDirectoriesSymlinkAware(configDir);
|
||||
|
||||
final YamlConfigurationLoader legacyLoader = ConfigurationLoaders.naturallySortedWithoutHeader(legacyConfig);
|
||||
final YamlConfigurationLoader globalLoader = ConfigurationLoaders.naturallySortedWithoutHeader(configDir.resolve(GLOBAL_CONFIG_FILE_NAME));
|
||||
final YamlConfigurationLoader worldDefaultsLoader = ConfigurationLoaders.naturallySortedWithoutHeader(configDir.resolve(WORLD_DEFAULTS_CONFIG_FILE_NAME));
|
||||
|
||||
final ConfigurationNode legacy = legacyLoader.load();
|
||||
checkState(!legacy.virtual(), "can't be virtual");
|
||||
final int version = legacy.node(Configuration.LEGACY_CONFIG_VERSION_FIELD).getInt();
|
||||
|
||||
final ConfigurationNode legacyWorldSettings = legacy.node("world-settings").copy();
|
||||
legacy.removeChild("world-settings");
|
||||
|
||||
// Apply legacy transformations new format change
|
||||
LegacySpigotConfig.migrate(legacy);
|
||||
final ConfigurationNode global = globalLoader.load();
|
||||
global.mergeFrom(legacy); // merge converted spigot global into main global
|
||||
liftBelowZeroGeneration(legacyWorldSettings, global);
|
||||
globalLoader.save(global); // save converted node to new global location
|
||||
|
||||
if (legacyWorldSettings.virtual()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final ConfigurationNode worldDefaults = legacyWorldSettings.node("default").copy();
|
||||
legacyWorldSettings.removeChild("default");
|
||||
if (!worldDefaults.virtual()) {
|
||||
worldDefaults.node(Configuration.LEGACY_CONFIG_VERSION_FIELD).raw(version);
|
||||
LegacySpigotWorldConfig.migrate(worldDefaults, "spigot.yml world-settings.default");
|
||||
final ConfigurationNode defaults = worldDefaultsLoader.load();
|
||||
defaults.mergeFrom(worldDefaults);
|
||||
worldDefaultsLoader.save(defaults);
|
||||
}
|
||||
|
||||
legacyWorldSettings.childrenMap().forEach((world, legacyWorldNode) -> {
|
||||
final String key = world.toString();
|
||||
try {
|
||||
final Path worldConfigDir = resolveWorldConfigDirectory(levelFolder, key);
|
||||
legacyWorldNode.node(Configuration.LEGACY_CONFIG_VERSION_FIELD).raw(version);
|
||||
LegacySpigotWorldConfig.migrate(legacyWorldNode, "spigot.yml world-settings." + key);
|
||||
final YamlConfigurationLoader worldLoader = ConfigurationLoaders.naturallySortedWithoutHeader(worldConfigDir.resolve(WORLD_CONFIG_FILE_NAME));
|
||||
final ConfigurationNode worldNode = worldLoader.load();
|
||||
worldNode.mergeFrom(legacyWorldNode);
|
||||
createDirectoriesSymlinkAware(worldConfigDir);
|
||||
worldLoader.save(worldNode);
|
||||
} catch (final Exception ex) {
|
||||
LOGGER.error("Failed to convert the spigot.yml world settings for '{}'. They have been left in {} for you to move over by hand.", key, legacyConfig, ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void liftBelowZeroGeneration(final ConfigurationNode legacyWorldSettings, final ConfigurationNode global) throws ConfigurateException {
|
||||
if (legacyWorldSettings.virtual()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Moved into the global config
|
||||
final String key = "below-zero-generation-in-existing-chunks";
|
||||
final ConfigurationNode target = global.node("misc", key);
|
||||
final ConfigurationNode fromDefault = legacyWorldSettings.node("default", key);
|
||||
if (!fromDefault.virtual() && target.virtual()) {
|
||||
target.set(fromDefault.raw());
|
||||
}
|
||||
for (final java.util.Map.Entry<Object, ? extends ConfigurationNode> entry : legacyWorldSettings.childrenMap().entrySet()) {
|
||||
entry.getValue().removeChild(key);
|
||||
}
|
||||
}
|
||||
|
||||
private static Path resolveWorldConfigDirectory(final Path levelFolder, final String worldKey) {
|
||||
final int separator = worldKey.indexOf(':');
|
||||
if (separator < 0) {
|
||||
return levelFolder.resolveSibling(worldKey);
|
||||
}
|
||||
return levelFolder.resolve("dimensions").resolve(worldKey.substring(0, separator)).resolve(worldKey.substring(separator + 1));
|
||||
}
|
||||
|
||||
private static String levelName(final OptionSet options) {
|
||||
final Object fromArgs = options.valueOf("world");
|
||||
if (fromArgs instanceof final String name && !name.isEmpty()) {
|
||||
return name;
|
||||
}
|
||||
final Path propertiesFile = ((File) options.valueOf("config")).toPath();
|
||||
if (Files.isRegularFile(propertiesFile)) {
|
||||
final java.util.Properties properties = new java.util.Properties();
|
||||
try (final java.io.Reader reader = Files.newBufferedReader(propertiesFile, StandardCharsets.UTF_8)) {
|
||||
properties.load(reader);
|
||||
} catch (final IOException ex) {
|
||||
LOGGER.warn("Could not read {} while locating world config files, assuming the default level name", propertiesFile, ex);
|
||||
}
|
||||
return properties.getProperty("level-name", "world");
|
||||
}
|
||||
return "world";
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies every leaf of {@code source} into {@code target}, replacing values that are still the
|
||||
* sentinel meaning "defer to bukkit.yml" ({@code -1}, or {@code default}). A world value the admin
|
||||
* had already set to something real keeps winning, as it did before.
|
||||
*/
|
||||
private static void overwriteDeferredValues(final ConfigurationNode source, final ConfigurationNode target) throws SerializationException {
|
||||
if (source.isMap()) {
|
||||
for (final java.util.Map.Entry<Object, ? extends ConfigurationNode> entry : source.childrenMap().entrySet()) {
|
||||
overwriteDeferredValues(entry.getValue(), target.node(entry.getKey()));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (target.virtual() || target.getInt(0) < 0 || "default".equals(target.getString())) {
|
||||
target.set(source.raw());
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean needsConverting(final Path legacyConfig) {
|
||||
return Files.exists(legacyConfig) && Files.isRegularFile(legacyConfig);
|
||||
}
|
||||
|
||||
private static YamlConfiguration loadLegacyConfigFile(final Path configFile) throws IOException {
|
||||
final YamlConfiguration config = new YamlConfiguration();
|
||||
if (Files.exists(configFile)) {
|
||||
try {
|
||||
config.load(Files.newBufferedReader(configFile, StandardCharsets.UTF_8));
|
||||
} catch (final Exception ex) {
|
||||
throw new IOException("Failed to load configuration file: " + configFile.toAbsolutePath(), ex);
|
||||
}
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
private PaperConfigurationsInitializer() {}
|
||||
}
|
||||
+1
-2
@@ -1,6 +1,5 @@
|
||||
package io.papermc.paper.configuration;
|
||||
|
||||
import org.spigotmc.SpigotConfig;
|
||||
|
||||
public class PaperServerConfiguration implements ServerConfiguration {
|
||||
|
||||
@@ -11,6 +10,6 @@ public class PaperServerConfiguration implements ServerConfiguration {
|
||||
|
||||
@Override
|
||||
public boolean isProxyEnabled() {
|
||||
return GlobalConfiguration.get().proxies.velocity.enabled || SpigotConfig.bungee;
|
||||
return GlobalConfiguration.get().proxies.velocity.enabled || io.papermc.paper.configuration.GlobalConfiguration.get().proxies.bungeeCord.enabled;
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -85,11 +85,12 @@ interface RemovedConfigurations {
|
||||
path("commands", "fix-target-selector-tag-completion"),
|
||||
path("misc", "fix-entity-position-desync"),
|
||||
path("chunk-system", "gen-parallelism"),
|
||||
path("logging"), // server is no longer obfuscated since 26.1
|
||||
path("logging", "deobfuscate-stacktraces"), // server is no longer obfuscated since 26.1
|
||||
path("unsupported-settings", "compression-format"),
|
||||
path("commands", "time-command-affects-all-worlds"), // replaced with time.affects-all-worlds
|
||||
path("misc", "fix-far-end-terrain-generation"),
|
||||
path("misc", "send-full-pos-for-item-entities")
|
||||
path("misc", "send-full-pos-for-item-entities"),
|
||||
path("timings"),
|
||||
};
|
||||
|
||||
}
|
||||
+218
-16
@@ -4,8 +4,6 @@ import com.google.common.collect.HashBasedTable;
|
||||
import com.google.common.collect.Table;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import io.papermc.paper.FeatureHooks;
|
||||
import io.papermc.paper.configuration.legacy.MaxEntityCollisionsInitializer;
|
||||
import io.papermc.paper.configuration.legacy.RequiresSpigotInitialization;
|
||||
import io.papermc.paper.configuration.mapping.MergeMap;
|
||||
import io.papermc.paper.configuration.serializer.NbtPathSerializer;
|
||||
import io.papermc.paper.configuration.serializer.collection.map.ThrowExceptions;
|
||||
@@ -16,10 +14,10 @@ import io.papermc.paper.configuration.type.Duration;
|
||||
import io.papermc.paper.configuration.type.DurationOrDisabled;
|
||||
import io.papermc.paper.configuration.type.EngineMode;
|
||||
import io.papermc.paper.configuration.type.fallback.ArrowDespawnRate;
|
||||
import io.papermc.paper.configuration.type.fallback.AutosavePeriod;
|
||||
import io.papermc.paper.configuration.type.number.BelowZeroToEmpty;
|
||||
import io.papermc.paper.configuration.type.number.DoubleOr;
|
||||
import io.papermc.paper.configuration.type.number.IntOr;
|
||||
import io.papermc.paper.configuration.type.number.LongOr;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2LongMap;
|
||||
@@ -60,7 +58,6 @@ import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.levelgen.feature.Feature;
|
||||
import org.slf4j.Logger;
|
||||
import org.spigotmc.SpigotWorldConfig;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||
import org.spongepowered.configurate.objectmapping.meta.PostProcess;
|
||||
@@ -71,13 +68,11 @@ import org.spongepowered.configurate.serialize.SerializationException;
|
||||
@SuppressWarnings({"FieldCanBeLocal", "FieldMayBeFinal", "NotNullFieldNotInitialized", "InnerClassMayBeStatic"})
|
||||
public class WorldConfiguration extends ConfigurationPart {
|
||||
private static final Logger LOGGER = LogUtils.getClassLogger();
|
||||
static final int CURRENT_VERSION = 31; // (when you change the version, change the comment, so it conflicts on rebases): migrate spawn loaded configs to gamerule
|
||||
static final int CURRENT_VERSION = 32; // (when you change the version, change the comment, so it conflicts on rebases): merge configs
|
||||
|
||||
private final transient SpigotWorldConfig spigotConfig;
|
||||
private final transient Identifier worldKey;
|
||||
|
||||
WorldConfiguration(final SpigotWorldConfig spigotConfig, final Identifier worldKey) {
|
||||
this.spigotConfig = spigotConfig;
|
||||
WorldConfiguration(final Identifier worldKey) {
|
||||
this.worldKey = worldKey;
|
||||
}
|
||||
|
||||
@@ -170,8 +165,35 @@ public class WorldConfiguration extends ConfigurationPart {
|
||||
public Spawning spawning;
|
||||
|
||||
public class Spawning extends ConfigurationPart {
|
||||
public ArrowDespawnRate nonPlayerArrowDespawnRate = ArrowDespawnRate.def(WorldConfiguration.this.spigotConfig);
|
||||
public ArrowDespawnRate creativeArrowDespawnRate = ArrowDespawnRate.def(WorldConfiguration.this.spigotConfig);
|
||||
@Comment("The radius around a player, in chunks, that mobs may naturally spawn in.")
|
||||
public int mobSpawnRange = 8;
|
||||
@Comment("Mobs spawned by monster spawners.")
|
||||
public SpawnerMobs spawnerMobs;
|
||||
|
||||
public class SpawnerMobs extends ConfigurationPart {
|
||||
@Comment("Spawn them without AI.")
|
||||
public boolean nerf = false;
|
||||
@Comment("Whether nerfed mobs still jump and float in water.")
|
||||
public boolean nerfedShouldJump = false;
|
||||
}
|
||||
|
||||
@Comment("Zombified piglins spawned by nether portals.")
|
||||
public PortalZombifiedPiglins portalZombifiedPiglins;
|
||||
|
||||
public class PortalZombifiedPiglins extends ConfigurationPart {
|
||||
public boolean enabled = true;
|
||||
@Comment("Spawn them without AI.")
|
||||
public boolean nerf = false;
|
||||
}
|
||||
|
||||
@Comment("Ticks before a dropped item despawns.")
|
||||
public int itemDespawnRate = 6000;
|
||||
@Comment("Ticks before a stuck arrow despawns.")
|
||||
public int arrowDespawnRate = 1200;
|
||||
@Comment("Ticks before a stuck trident despawns. Defaults to the arrow despawn rate.")
|
||||
public IntOr.Default tridentDespawnRate = IntOr.Default.USE_DEFAULT;
|
||||
public ArrowDespawnRate nonPlayerArrowDespawnRate = ArrowDespawnRate.def(WorldConfiguration.this);
|
||||
public ArrowDespawnRate creativeArrowDespawnRate = ArrowDespawnRate.def(WorldConfiguration.this);
|
||||
public IntOr.Disabled maxArrowDespawnInvulnerability = new IntOr.Disabled(OptionalInt.of(200));
|
||||
public boolean filterBadTileEntityNbtFromFallingBlocks = true;
|
||||
public List<NbtPathArgument.NbtPath> filteredEntityTagNbtPaths = NbtPathSerializer.fromString(List.of("Pos", "Motion", "sleeping_pos"));
|
||||
@@ -179,12 +201,28 @@ public class WorldConfiguration extends ConfigurationPart {
|
||||
public boolean perPlayerMobSpawns = true;
|
||||
public boolean scanForLegacyEnderDragon = true;
|
||||
@MergeMap
|
||||
public Reference2IntMap<MobCategory> spawnLimits = Util.make(new Reference2IntOpenHashMap<>(NaturalSpawner.SPAWNING_CATEGORIES.length), map -> Arrays.stream(NaturalSpawner.SPAWNING_CATEGORIES).forEach(mobCategory -> map.put(mobCategory, -1)));
|
||||
public Reference2IntMap<MobCategory> spawnLimits = Util.make(new Reference2IntOpenHashMap<>(NaturalSpawner.SPAWNING_CATEGORIES.length), map -> {
|
||||
map.put(MobCategory.MONSTER, 70);
|
||||
map.put(MobCategory.CREATURE, 10);
|
||||
map.put(MobCategory.WATER_CREATURE, 5);
|
||||
map.put(MobCategory.WATER_AMBIENT, 20);
|
||||
map.put(MobCategory.UNDERGROUND_WATER_CREATURE, 5);
|
||||
map.put(MobCategory.AXOLOTLS, 5);
|
||||
map.put(MobCategory.AMBIENT, 15);
|
||||
});
|
||||
@MergeMap
|
||||
public Map<MobCategory, DespawnRangePair> despawnRanges = Arrays.stream(MobCategory.values()).collect(Collectors.toMap(Function.identity(), category -> DespawnRangePair.createDefault()));
|
||||
public DespawnRange.Shape despawnRangeShape = DespawnRange.Shape.ELLIPSOID;
|
||||
@MergeMap
|
||||
public Reference2IntMap<MobCategory> ticksPerSpawn = Util.make(new Reference2IntOpenHashMap<>(NaturalSpawner.SPAWNING_CATEGORIES.length), map -> Arrays.stream(NaturalSpawner.SPAWNING_CATEGORIES).forEach(mobCategory -> map.put(mobCategory, -1)));
|
||||
public Reference2IntMap<MobCategory> ticksPerSpawn = Util.make(new Reference2IntOpenHashMap<>(NaturalSpawner.SPAWNING_CATEGORIES.length), map -> {
|
||||
map.put(MobCategory.MONSTER, 1);
|
||||
map.put(MobCategory.CREATURE, 400);
|
||||
map.put(MobCategory.WATER_CREATURE, 1);
|
||||
map.put(MobCategory.WATER_AMBIENT, 1);
|
||||
map.put(MobCategory.UNDERGROUND_WATER_CREATURE, 1);
|
||||
map.put(MobCategory.AXOLOTLS, 1);
|
||||
map.put(MobCategory.AMBIENT, 1);
|
||||
});
|
||||
|
||||
@ConfigSerializable
|
||||
public record DespawnRangePair(@Required DespawnRange hard, @Required DespawnRange soft) {
|
||||
@@ -275,7 +313,6 @@ public class WorldConfiguration extends ConfigurationPart {
|
||||
|
||||
public class Behavior extends ConfigurationPart {
|
||||
public boolean disableChestCatDetection = false;
|
||||
public boolean spawnerNerfedMobsShouldJump = false;
|
||||
public int experienceMergeMaxValue = -1;
|
||||
public boolean shouldRemoveDragon = false;
|
||||
public boolean zombiesTargetTurtleEggs = true;
|
||||
@@ -310,7 +347,6 @@ public class WorldConfiguration extends ConfigurationPart {
|
||||
}
|
||||
|
||||
public boolean disablePlayerCrits = false;
|
||||
public boolean nerfPigmenFromNetherPortals = false;
|
||||
@Comment("Prevents merging items that are not on the same y level, preventing potential visual artifacts.")
|
||||
public boolean onlyMergeItemsHorizontally = false;
|
||||
public PillagerPatrols pillagerPatrols;
|
||||
@@ -338,6 +374,67 @@ public class WorldConfiguration extends ConfigurationPart {
|
||||
public IntOr.Disabled stuckEntityPoiRetryDelay = new IntOr.Disabled(OptionalInt.of(200));
|
||||
@Comment("If specified, this setting controls how often to tick inactive mobs' goal selectors. Higher values reduce the performance impact of ticking mobs that are far away. Measured in ticks.")
|
||||
public IntOr.Disabled inactiveMobGoalSelectorThrottleRate = IntOr.Disabled.DISABLED;
|
||||
@Comment("Whether zombies are always hostile towards villagers, regardless of difficulty.")
|
||||
public boolean zombieAggressiveTowardsVillager = true;
|
||||
|
||||
@Comment("The radius within which dropped items and experience orbs merge. Set to -1 to disable.")
|
||||
public MergeRadius mergeRadius;
|
||||
|
||||
public class MergeRadius extends ConfigurationPart {
|
||||
public double item = 0.5;
|
||||
public double experience = -1.0;
|
||||
}
|
||||
}
|
||||
|
||||
@Comment("The horizontal distance, in blocks, at which entities of each category are sent to players.")
|
||||
public TrackingRange trackingRange;
|
||||
|
||||
public class TrackingRange extends ConfigurationPart {
|
||||
public int player = 128;
|
||||
public int animal = 96;
|
||||
public int monster = 96;
|
||||
public int misc = 96;
|
||||
public int display = 128;
|
||||
public int other = 64;
|
||||
}
|
||||
|
||||
@Comment("The distance, in blocks, at which entities of each category are ticked. Entities outside it are mostly frozen.")
|
||||
public ActivationRange activationRange;
|
||||
|
||||
public class ActivationRange extends ConfigurationPart {
|
||||
public int animals = 32;
|
||||
public int monsters = 32;
|
||||
public int raiders = 64;
|
||||
public int misc = 16;
|
||||
public int water = 16;
|
||||
public int villagers = 32;
|
||||
public int flyingMonsters = 32;
|
||||
@Comment("Ticks a villager is immune to deactivation after starting work.")
|
||||
public int villagersWorkImmunityAfter = 100;
|
||||
public int villagersWorkImmunityFor = 20;
|
||||
@Comment("Whether villagers stay active while panicking.")
|
||||
public boolean villagersActiveForPanic = true;
|
||||
public boolean tickInactiveVillagers = true;
|
||||
@Comment("Whether spectators keep nearby entities active.")
|
||||
public boolean ignoreSpectators = false;
|
||||
|
||||
@Comment("Periodically wakes a limited number of deactivated entities so they do not stay frozen forever.")
|
||||
public WakeUpInactive wakeUpInactive;
|
||||
|
||||
public class WakeUpInactive extends ConfigurationPart {
|
||||
public int animalsMaxPerTick = 4;
|
||||
public int animalsEvery = 1200;
|
||||
public int animalsFor = 100;
|
||||
public int monstersMaxPerTick = 8;
|
||||
public int monstersEvery = 400;
|
||||
public int monstersFor = 100;
|
||||
public int villagersMaxPerTick = 4;
|
||||
public int villagersEvery = 600;
|
||||
public int villagersFor = 100;
|
||||
public int flyingMonstersMaxPerTick = 8;
|
||||
public int flyingMonstersEvery = 200;
|
||||
public int flyingMonstersFor = 100;
|
||||
}
|
||||
}
|
||||
|
||||
public TrackingRangeY trackingRangeY;
|
||||
@@ -409,10 +506,87 @@ public class WorldConfiguration extends ConfigurationPart {
|
||||
public boolean useVanillaWorldScoreboardNameColoring = false;
|
||||
}
|
||||
|
||||
@Comment("""
|
||||
Percentage chance that a growth tick actually advances the plant.
|
||||
100 is vanilla speed, 50 is half as fast, 200 is twice as fast.""")
|
||||
public GrowthModifiers growthModifiers;
|
||||
|
||||
public class GrowthModifiers extends ConfigurationPart {
|
||||
public int cactus = 100;
|
||||
public int cane = 100;
|
||||
public int melon = 100;
|
||||
public int mushroom = 100;
|
||||
public int pumpkin = 100;
|
||||
public int sapling = 100;
|
||||
public int beetroot = 100;
|
||||
public int carrot = 100;
|
||||
public int potato = 100;
|
||||
public int torchFlower = 100;
|
||||
public int wheat = 100;
|
||||
public int netherWart = 100;
|
||||
public int vine = 100;
|
||||
public int cocoa = 100;
|
||||
public int bamboo = 100;
|
||||
public int sweetBerry = 100;
|
||||
public int kelp = 100;
|
||||
public int twistingVines = 100;
|
||||
public int weepingVines = 100;
|
||||
public int caveVines = 100;
|
||||
public int glowBerry = 100;
|
||||
public int pitcherPlant = 100;
|
||||
}
|
||||
|
||||
@Comment("Exhaustion added by each action. Higher values make the hunger bar drain faster.")
|
||||
public Hunger hunger;
|
||||
|
||||
public class Hunger extends ConfigurationPart {
|
||||
public float jumpWalkExhaustion = 0.05f;
|
||||
public float jumpSprintExhaustion = 0.2f;
|
||||
public float combatExhaustion = 0.1f;
|
||||
public float regenExhaustion = 6.0f;
|
||||
public float swimMultiplier = 0.01f;
|
||||
public float sprintMultiplier = 0.1f;
|
||||
public float otherMultiplier = 0.0f;
|
||||
}
|
||||
|
||||
@Comment("""
|
||||
Salts used when deciding where structures and other seeded features generate.
|
||||
Changing these changes where they appear in newly generated chunks.""")
|
||||
public Seeds seeds;
|
||||
|
||||
public class Seeds extends ConfigurationPart {
|
||||
public int village = 10387312;
|
||||
public int desert = 14357617;
|
||||
public int igloo = 14357618;
|
||||
public int jungle = 14357619;
|
||||
public int swamp = 14357620;
|
||||
public int monument = 10387313;
|
||||
public int shipwreck = 165745295;
|
||||
public int ocean = 14357621;
|
||||
public int outpost = 165745296;
|
||||
public int endCity = 10387313;
|
||||
public int slime = 987234911;
|
||||
public int nether = 30084232;
|
||||
public int mansion = 10387319;
|
||||
public int fossil = 14357921;
|
||||
public int portal = 34222645;
|
||||
public int ancientCity = 20083232;
|
||||
public int trailRuins = 83469867;
|
||||
public int trialChambers = 94251327;
|
||||
public int abandonedCamp = 91231127;
|
||||
public int buriedTreasure = 10387320;
|
||||
@Comment("Set to 'default' to use the vanilla value.")
|
||||
public IntOr.Default mineshaft = IntOr.Default.USE_DEFAULT;
|
||||
@Comment("Set to 'default' to use the vanilla value.")
|
||||
public LongOr.Default stronghold = LongOr.Default.USE_DEFAULT;
|
||||
}
|
||||
|
||||
public Environment environment;
|
||||
|
||||
public class Environment extends ConfigurationPart {
|
||||
public boolean disableThunder = false;
|
||||
@Comment("The 1-in-N chance per tick of a thunderstorm starting. Higher means less frequent.")
|
||||
public int thunderChance = 100000;
|
||||
public boolean disableIceAndSnow = false;
|
||||
public boolean optimizeExplosions = false;
|
||||
public boolean disableExplosionKnockback = false;
|
||||
@@ -468,6 +642,8 @@ public class WorldConfiguration extends ConfigurationPart {
|
||||
|
||||
public class Fixes extends ConfigurationPart {
|
||||
public boolean fixItemsMergingThroughWalls = false;
|
||||
@Comment("The maximum number of primed TNT entities ticked per tick. Set to -1 to disable the limit.")
|
||||
public int maxTntPerTick = 100;
|
||||
public boolean disableUnloadedChunkEnderpearlExploit = false;
|
||||
public boolean preventTntFromMovingInWater = false;
|
||||
public boolean splitOverstackedLoot = true;
|
||||
@@ -492,6 +668,10 @@ public class WorldConfiguration extends ConfigurationPart {
|
||||
|
||||
public class Hopper extends ConfigurationPart {
|
||||
public boolean cooldownWhenFull = true;
|
||||
@Comment("The number of items moved per hopper transfer.")
|
||||
public int amount = 1;
|
||||
@Comment("Whether hoppers may load chunks to pull from or push into containers across a chunk border.")
|
||||
public boolean canLoadChunks = false;
|
||||
public boolean disableMoveEvent = false;
|
||||
public boolean ignoreOccludingBlocks = false;
|
||||
}
|
||||
@@ -502,7 +682,6 @@ public class WorldConfiguration extends ConfigurationPart {
|
||||
public boolean onlyPlayersCollide = false;
|
||||
public boolean allowVehicleCollisions = true;
|
||||
public boolean fixClimbingBypassingCrammingRule = false;
|
||||
@RequiresSpigotInitialization(MaxEntityCollisionsInitializer.class)
|
||||
public int maxEntityCollisions = 8;
|
||||
public boolean allowPlayerCrammingDamage = false;
|
||||
}
|
||||
@@ -510,7 +689,14 @@ public class WorldConfiguration extends ConfigurationPart {
|
||||
public Chunks chunks;
|
||||
|
||||
public class Chunks extends ConfigurationPart {
|
||||
public AutosavePeriod autoSaveInterval = AutosavePeriod.def();
|
||||
@Comment("Ticks between saves of this world. Set to 0 or below to disable automatic saving.")
|
||||
public int autoSaveInterval = 6000;
|
||||
@Comment("Per-world view distance. Defaults to the server.properties value.")
|
||||
public IntOr.Default viewDistance = IntOr.Default.USE_DEFAULT;
|
||||
@Comment("Per-world simulation distance. Defaults to the server.properties value.")
|
||||
public IntOr.Default simulationDistance = IntOr.Default.USE_DEFAULT;
|
||||
@Comment("Whether chunks kept loaded by a ticking-frozen world are unloaded.")
|
||||
public boolean unloadFrozenChunks = false;
|
||||
public int maxAutoSaveChunksPerTick = 24;
|
||||
public int fixedChunkInhabitedTime = -1;
|
||||
public int minChunkUnloadCount = 50;
|
||||
@@ -545,6 +731,13 @@ public class WorldConfiguration extends ConfigurationPart {
|
||||
|
||||
public class TickRates extends ConfigurationPart {
|
||||
public int grassSpread = 1;
|
||||
@Comment("Ticks between hopper transfer attempts.")
|
||||
public int hopperTransfer = 8;
|
||||
@Comment("Ticks between hopper pickup checks.")
|
||||
public int hopperCheck = 1;
|
||||
@Comment("Ticks between hanging entity (item frame, painting) validity checks.")
|
||||
public int hangingTickFrequency = 100;
|
||||
|
||||
public int containerUpdate = 1;
|
||||
public int mobSpawner = 1;
|
||||
public int wetFarmland = 1;
|
||||
@@ -579,6 +772,15 @@ public class WorldConfiguration extends ConfigurationPart {
|
||||
public Misc misc;
|
||||
|
||||
public class Misc extends ConfigurationPart {
|
||||
@Comment("Radius in blocks that these sounds are broadcast to. Set to 0 to use the vanilla behaviour.")
|
||||
public SoundRadius soundRadius;
|
||||
|
||||
public class SoundRadius extends ConfigurationPart {
|
||||
public int dragonDeath = 0;
|
||||
public int witherSpawn = 0;
|
||||
public int endPortal = 0;
|
||||
}
|
||||
|
||||
public boolean updatePathfindingOnBlockUpdate = true;
|
||||
public boolean showSignClickCommandFailureMsgsToPlayer = false;
|
||||
public RedstoneImplementation redstoneImplementation = RedstoneImplementation.VANILLA;
|
||||
|
||||
+57
-11
@@ -1,22 +1,33 @@
|
||||
package io.papermc.paper.configuration.constraint;
|
||||
|
||||
import io.papermc.paper.configuration.type.number.DoubleOr;
|
||||
import io.papermc.paper.configuration.type.number.IntOr;
|
||||
import io.papermc.paper.configuration.type.number.LongOr;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.reflect.Type;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.OptionalDouble;
|
||||
import java.util.OptionalInt;
|
||||
import java.util.OptionalLong;
|
||||
import java.util.function.Function;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Constraint;
|
||||
import org.spongepowered.configurate.serialize.SerializationException;
|
||||
|
||||
import static io.leangen.geantyref.GenericTypeReflector.box;
|
||||
import static io.leangen.geantyref.GenericTypeReflector.erase;
|
||||
|
||||
public final class Constraints {
|
||||
private Constraints() {
|
||||
}
|
||||
|
||||
public static final class Positive implements Constraint<Number> {
|
||||
@Override
|
||||
public void validate(@Nullable Number value) throws SerializationException {
|
||||
public void validate(final @Nullable Number value) throws SerializationException {
|
||||
if (value != null && value.doubleValue() <= 0) {
|
||||
throw new SerializationException(value + " should be positive");
|
||||
}
|
||||
@@ -27,14 +38,19 @@ public final class Constraints {
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface Min {
|
||||
int value();
|
||||
double value();
|
||||
|
||||
final class Factory implements Constraint.Factory<Min, Number> {
|
||||
final class Factory implements Constraint.Factory<Min, Object> {
|
||||
@Override
|
||||
public Constraint<Number> make(Min data, Type type) {
|
||||
public Constraint<Object> make(final Min data, final Type type) {
|
||||
final Function<Object, OptionalDouble> extractor = numberExtractor(type);
|
||||
return value -> {
|
||||
if (value != null && value.intValue() < data.value()) {
|
||||
throw new SerializationException(value + " is less than the min " + data.value());
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
final OptionalDouble number = extractor.apply(value);
|
||||
if (number.isPresent() && number.getAsDouble() < data.value()) {
|
||||
throw new SerializationException(format(number.getAsDouble()) + " is less than the min " + format(data.value()));
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -45,17 +61,47 @@ public final class Constraints {
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface Max {
|
||||
int value();
|
||||
double value();
|
||||
|
||||
final class Factory implements Constraint.Factory<Max, Number> {
|
||||
final class Factory implements Constraint.Factory<Max, Object> {
|
||||
@Override
|
||||
public Constraint<Number> make(Max data, Type type) {
|
||||
public Constraint<Object> make(final Max data, final Type type) {
|
||||
final Function<Object, OptionalDouble> extractor = numberExtractor(type);
|
||||
return value -> {
|
||||
if (value != null && value.intValue() > data.value()) {
|
||||
throw new SerializationException(value + " is greater than the max " + data.value());
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
final OptionalDouble number = extractor.apply(value);
|
||||
if (number.isPresent() && number.getAsDouble() > data.value()) {
|
||||
throw new SerializationException(format(number.getAsDouble()) + " is greater than the max " + format(data.value()));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// empty for the "default"/"disabled" values of the NumberOr types, which are not range checked
|
||||
private static Function<Object, OptionalDouble> numberExtractor(final Type type) {
|
||||
final Class<?> erased = erase(box(type));
|
||||
if (Number.class.isAssignableFrom(erased)) {
|
||||
return value -> OptionalDouble.of(((Number) value).doubleValue());
|
||||
} else if (IntOr.class.isAssignableFrom(erased)) {
|
||||
return value -> {
|
||||
final OptionalInt number = ((IntOr) value).value();
|
||||
return number.isPresent() ? OptionalDouble.of(number.getAsInt()) : OptionalDouble.empty();
|
||||
};
|
||||
} else if (LongOr.class.isAssignableFrom(erased)) {
|
||||
return value -> {
|
||||
final OptionalLong number = ((LongOr) value).value();
|
||||
return number.isPresent() ? OptionalDouble.of(number.getAsLong()) : OptionalDouble.empty();
|
||||
};
|
||||
} else if (DoubleOr.class.isAssignableFrom(erased)) {
|
||||
return value -> ((DoubleOr) value).value();
|
||||
}
|
||||
throw new IllegalArgumentException(type.getTypeName() + " is not a number type that supports range constraints");
|
||||
}
|
||||
|
||||
private static String format(final double value) {
|
||||
return BigDecimal.valueOf(value).stripTrailingZeros().toPlainString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
@NullMarked
|
||||
package io.papermc.paper.configuration.constraint;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
-29
@@ -1,29 +0,0 @@
|
||||
package io.papermc.paper.configuration.legacy;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.spigotmc.SpigotWorldConfig;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.objectmapping.meta.NodeResolver;
|
||||
import org.spongepowered.configurate.util.NamingSchemes;
|
||||
|
||||
public class MaxEntityCollisionsInitializer implements NodeResolver {
|
||||
|
||||
private final String name;
|
||||
private final SpigotWorldConfig spigotConfig;
|
||||
|
||||
public MaxEntityCollisionsInitializer(String name, SpigotWorldConfig spigotConfig) {
|
||||
this.name = name;
|
||||
this.spigotConfig = spigotConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ConfigurationNode resolve(ConfigurationNode parent) {
|
||||
final String key = NamingSchemes.LOWER_CASE_DASHED.coerce(this.name);
|
||||
final ConfigurationNode node = parent.node(key);
|
||||
final int old = this.spigotConfig.getInt("max-entity-collisions", -1, false);
|
||||
if (node.virtual() && old > -1) {
|
||||
node.raw(old);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
}
|
||||
-48
@@ -1,48 +0,0 @@
|
||||
package io.papermc.paper.configuration.legacy;
|
||||
|
||||
import com.google.common.collect.HashBasedTable;
|
||||
import com.google.common.collect.Table;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Constructor;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.spigotmc.SpigotWorldConfig;
|
||||
import org.spongepowered.configurate.objectmapping.meta.NodeResolver;
|
||||
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface RequiresSpigotInitialization {
|
||||
|
||||
Class<? extends NodeResolver> value();
|
||||
|
||||
final class Factory implements NodeResolver.Factory {
|
||||
|
||||
private final SpigotWorldConfig spigotWorldConfig;
|
||||
private final Table<Class<? extends NodeResolver>, String, NodeResolver> cache = HashBasedTable.create();
|
||||
|
||||
public Factory(SpigotWorldConfig spigotWorldConfig) {
|
||||
this.spigotWorldConfig = spigotWorldConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable NodeResolver make(String name, AnnotatedElement element) {
|
||||
if (element.isAnnotationPresent(RequiresSpigotInitialization.class)) {
|
||||
return this.cache.row(element.getAnnotation(RequiresSpigotInitialization.class).value()).computeIfAbsent(name, key -> {
|
||||
try {
|
||||
final Constructor<? extends NodeResolver> constructor = element.getAnnotation(RequiresSpigotInitialization.class).value().getDeclaredConstructor(String.class, SpigotWorldConfig.class);
|
||||
constructor.trySetAccessible();
|
||||
return constructor.newInstance(key, this.spigotWorldConfig);
|
||||
} catch (final ReflectiveOperationException e) {
|
||||
throw new RuntimeException("Could not create constraint", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
package io.papermc.paper.configuration.legacy;
|
||||
|
||||
import org.spigotmc.SpigotWorldConfig;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.objectmapping.meta.NodeResolver;
|
||||
import org.spongepowered.configurate.util.NamingSchemes;
|
||||
|
||||
public final class SpawnLoadedRangeInitializer implements NodeResolver {
|
||||
|
||||
private final String name;
|
||||
private final SpigotWorldConfig spigotConfig;
|
||||
|
||||
public SpawnLoadedRangeInitializer(String name, SpigotWorldConfig spigotConfig) {
|
||||
this.name = name;
|
||||
this.spigotConfig = spigotConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigurationNode resolve(ConfigurationNode parent) {
|
||||
final String key = NamingSchemes.LOWER_CASE_DASHED.coerce(this.name);
|
||||
final ConfigurationNode node = parent.node(key);
|
||||
if (node.virtual()) {
|
||||
node.raw(Math.min(spigotConfig.viewDistance, 10));
|
||||
}
|
||||
return node;
|
||||
}
|
||||
}
|
||||
+30
-4
@@ -2,8 +2,10 @@ package io.papermc.paper.configuration.mapping;
|
||||
|
||||
import io.leangen.geantyref.GenericTypeReflector;
|
||||
import io.papermc.paper.configuration.ConfigurationPart;
|
||||
import io.papermc.paper.configuration.GlobalConfiguration;
|
||||
import io.papermc.paper.configuration.WorldConfiguration;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.AnnotatedType;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
@@ -12,6 +14,7 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.SequencedMap;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Supplier;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.spongepowered.configurate.objectmapping.FieldDiscoverer;
|
||||
@@ -25,11 +28,13 @@ public final class InnerClassFieldDiscoverer implements FieldDiscoverer<Map<Fiel
|
||||
private final InnerClassInstanceSupplier instanceSupplier;
|
||||
private final FieldDiscoverer<Map<Field, Object>> delegate;
|
||||
private final Map<Class<? extends Annotation>, List<Definition<?, ?, ? extends FieldProcessor.Factory<?, ?>>>> fieldProcessors;
|
||||
private final BiPredicate<AnnotatedType, AnnotatedElement> fieldFilter;
|
||||
|
||||
private InnerClassFieldDiscoverer(final InnerClassInstanceSupplier instanceSupplier, final FieldDiscoverer<Map<Field, Object>> delegate, final Map<Class<? extends Annotation>, List<Definition<?, ?, ? extends FieldProcessor.Factory<?, ?>>>> fieldProcessors) {
|
||||
private InnerClassFieldDiscoverer(final InnerClassInstanceSupplier instanceSupplier, final FieldDiscoverer<Map<Field, Object>> delegate, final Map<Class<? extends Annotation>, List<Definition<?, ?, ? extends FieldProcessor.Factory<?, ?>>>> fieldProcessors, final BiPredicate<AnnotatedType, AnnotatedElement> fieldFilter) {
|
||||
this.instanceSupplier = instanceSupplier;
|
||||
this.delegate = delegate;
|
||||
this.fieldProcessors = fieldProcessors;
|
||||
this.fieldFilter = fieldFilter;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
@@ -39,7 +44,7 @@ public final class InnerClassFieldDiscoverer implements FieldDiscoverer<Map<Fiel
|
||||
if (ConfigurationPart.class.isAssignableFrom(clazz)) {
|
||||
// we don't care about the returned instance factory, we just need to call it to collect fields
|
||||
final Object dummyInstance = this.delegate.<V>discover(target, (name, fieldType, container, deserializer, serializer) -> {
|
||||
if (!erase(fieldType.getType()).equals(clazz.getEnclosingClass())) { // don't collect synth fields for inner classes
|
||||
if (!erase(fieldType.getType()).equals(clazz.getEnclosingClass()) && this.fieldFilter.test(fieldType, container)) { // don't collect synth fields for inner classes
|
||||
FieldProcessor<?> processor = null;
|
||||
processor: for (final Annotation annotation : container.getAnnotations()) {
|
||||
final List<Definition<?, ?, ? extends FieldProcessor.Factory<?, ?>>> definitions = this.fieldProcessors.get(annotation.annotationType());
|
||||
@@ -80,14 +85,18 @@ public final class InnerClassFieldDiscoverer implements FieldDiscoverer<Map<Fiel
|
||||
record ImplicitProvider(Supplier<Object> provider) {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static InnerClassFieldDiscoverer create(final Map<Class<?>, Object> overrides, final List<Definition<?, ?, ? extends FieldProcessor.Factory<?, ?>>> fieldProcessors) {
|
||||
return create(overrides, fieldProcessors, (fieldType, container) -> true);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static InnerClassFieldDiscoverer create(final Map<Class<?>, Object> overrides, final List<Definition<?, ?, ? extends FieldProcessor.Factory<?, ?>>> fieldProcessors, final BiPredicate<AnnotatedType, AnnotatedElement> fieldFilter) {
|
||||
final Map<Class<? extends Annotation>, List<Definition<?, ?, ? extends FieldProcessor.Factory<?, ?>>>> processors = new LinkedHashMap<>();
|
||||
for (final Definition<?, ?, ? extends FieldProcessor.Factory<?, ?>> definition : fieldProcessors) {
|
||||
processors.computeIfAbsent(definition.annotation(), k -> new ArrayList<>()).add(definition);
|
||||
}
|
||||
final InnerClassInstanceSupplier instanceSupplier = new InnerClassInstanceSupplier(overrides);
|
||||
return new InnerClassFieldDiscoverer(instanceSupplier, (FieldDiscoverer<Map<Field, Object>>) FieldDiscoverer.object(instanceSupplier), processors);
|
||||
return new InnerClassFieldDiscoverer(instanceSupplier, (FieldDiscoverer<Map<Field, Object>>) FieldDiscoverer.object(instanceSupplier), processors, fieldFilter);
|
||||
}
|
||||
|
||||
public static FieldDiscoverer<?> worldConfig(final WorldConfiguration worldConfiguration, final List<Definition<?, ?, ? extends FieldProcessor.Factory<?, ?>>> fieldProcessors) {
|
||||
@@ -100,4 +109,21 @@ public final class InnerClassFieldDiscoverer implements FieldDiscoverer<Map<Fiel
|
||||
public static FieldDiscoverer<?> globalConfig(final List<Definition<?, ?, ? extends FieldProcessor.Factory<?, ?>>> fieldProcessors) {
|
||||
return create(Collections.emptyMap(), fieldProcessors);
|
||||
}
|
||||
|
||||
public static FieldDiscoverer<?> globalConfig(final GlobalConfiguration globalConfiguration, final List<Definition<?, ?, ? extends FieldProcessor.Factory<?, ?>>> fieldProcessors) {
|
||||
final Map<Class<?>, Object> overrides = Map.of(
|
||||
GlobalConfiguration.class, globalConfiguration
|
||||
);
|
||||
return create(overrides, fieldProcessors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Only discovers {@link LoadEarly} fields, and the sections containing them.
|
||||
*/
|
||||
public static FieldDiscoverer<?> earlyGlobalConfig(final GlobalConfiguration globalConfiguration, final List<Definition<?, ?, ? extends FieldProcessor.Factory<?, ?>>> fieldProcessors) {
|
||||
final Map<Class<?>, Object> overrides = Map.of(
|
||||
GlobalConfiguration.class, globalConfiguration
|
||||
);
|
||||
return create(overrides, fieldProcessors, LoadEarly.FIELD_FILTER);
|
||||
}
|
||||
}
|
||||
+10
-1
@@ -32,6 +32,7 @@ final class InnerClassInstanceFactory implements FieldDiscoverer.MutableInstance
|
||||
@Override
|
||||
public void complete(final Object instance, final Map<Field, DeserializedFieldInfo<?>> intermediate) throws SerializationException {
|
||||
// modeled off of native ObjectFieldDiscoverer
|
||||
SerializationException failure = null;
|
||||
for (final Map.Entry<Field, DeserializedFieldInfo<?>> entry : intermediate.entrySet()) {
|
||||
final boolean hasProcessor = entry.getValue().processor() != null;
|
||||
try {
|
||||
@@ -57,9 +58,17 @@ final class InnerClassInstanceFactory implements FieldDiscoverer.MutableInstance
|
||||
throw new SerializationException(this.targetType.getType(), e);
|
||||
} catch (final SerializationException ex) {
|
||||
ex.initType(entry.getValue().fieldType());
|
||||
|
||||
// like ObjectMapperImpl, keep going so every failing field is reported
|
||||
if (failure == null) {
|
||||
failure = ex;
|
||||
} else {
|
||||
failure.addSuppressed(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (failure != null) {
|
||||
throw failure;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package io.papermc.paper.configuration.mapping;
|
||||
|
||||
import io.papermc.paper.configuration.ConfigurationPart;
|
||||
import io.papermc.paper.configuration.GlobalConfiguration;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.AnnotatedType;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.function.BiPredicate;
|
||||
|
||||
import static io.leangen.geantyref.GenericTypeReflector.erase;
|
||||
|
||||
/**
|
||||
* Marks a field of the {@link GlobalConfiguration} that is deserialized early during startup,
|
||||
* before {@link net.minecraft.server.Bootstrap} has run and before a {@link net.minecraft.core.RegistryAccess} exists.
|
||||
* The same instance is populated again with every other field once the full config is loaded.
|
||||
* <p>
|
||||
* Every {@link ConfigurationPart} containing an early field is instantiated during the early load,
|
||||
* which runs <b>all</b> of its field initializers, not just the ones of early fields. Those must therefore
|
||||
* not touch anything that requires the game to be bootstrapped, like registries.
|
||||
* <p>
|
||||
* Early fields may only use registry-free types, and {@link org.spongepowered.configurate.objectmapping.meta.PostProcess}
|
||||
* methods are not run during the early load. Marking a {@link ConfigurationPart} field itself has no effect, mark its fields instead.
|
||||
*/
|
||||
@Documented
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface LoadEarly {
|
||||
|
||||
/**
|
||||
* Accepts only fields marked with {@link LoadEarly}, and {@link ConfigurationPart}s that (transitively) contain such a field.
|
||||
* Rejected sections are never instantiated.
|
||||
*/
|
||||
BiPredicate<AnnotatedType, AnnotatedElement> FIELD_FILTER = new FieldFilter();
|
||||
|
||||
final class FieldFilter implements BiPredicate<AnnotatedType, AnnotatedElement> {
|
||||
|
||||
private static final ClassValue<Boolean> CONTAINS_EARLY_FIELDS = new ClassValue<>() {
|
||||
@Override
|
||||
protected Boolean computeValue(final Class<?> type) {
|
||||
for (Class<?> clazz = type; clazz != null && clazz != Object.class; clazz = clazz.getSuperclass()) {
|
||||
for (final Field field : clazz.getDeclaredFields()) {
|
||||
if (field.isSynthetic() || Modifier.isStatic(field.getModifiers()) || Modifier.isTransient(field.getModifiers())) {
|
||||
continue;
|
||||
}
|
||||
if (field.isAnnotationPresent(LoadEarly.class) || isEarlySection(field.getType())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
private FieldFilter() {
|
||||
}
|
||||
|
||||
private static boolean isEarlySection(final Class<?> type) {
|
||||
return ConfigurationPart.class.isAssignableFrom(type) && CONTAINS_EARLY_FIELDS.get(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(final AnnotatedType fieldType, final AnnotatedElement container) {
|
||||
return container.isAnnotationPresent(LoadEarly.class) || isEarlySection(erase(fieldType.getType()));
|
||||
}
|
||||
}
|
||||
}
|
||||
-1
@@ -172,5 +172,4 @@ public class MapSerializer implements TypeSerializer.Annotated<Map<?, ?>> {
|
||||
}
|
||||
return new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package io.papermc.paper.configuration.transformation;
|
||||
|
||||
import io.papermc.paper.configuration.Configurations.ContextKey;
|
||||
import io.papermc.paper.configuration.Configurations.ContextMap;
|
||||
import java.util.Set;
|
||||
import org.spongepowered.configurate.transformation.ConfigurationTransformation;
|
||||
|
||||
/**
|
||||
* A versioned transformation that declares the context it needs instead of reaching for global state,
|
||||
* so it can be left out of loads where that context is not available yet.
|
||||
*/
|
||||
public interface ContextualTransformation {
|
||||
|
||||
int version();
|
||||
|
||||
/**
|
||||
* Keys that must be present in the {@link ContextMap} passed to {@link #create(ContextMap)}.
|
||||
*
|
||||
* @return required context keys
|
||||
*/
|
||||
default Set<ContextKey<?>> requiredContext() {
|
||||
return Set.of();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the transformation.
|
||||
*
|
||||
* @param context context containing at least every key in {@link #requiredContext()}
|
||||
* @return the transformation
|
||||
*/
|
||||
ConfigurationTransformation create(ContextMap context);
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package io.papermc.paper.configuration.transformation;
|
||||
|
||||
import java.util.Properties;
|
||||
import java.util.function.Consumer;
|
||||
import net.minecraft.server.dedicated.DedicatedServer;
|
||||
import net.minecraft.server.dedicated.DedicatedServerProperties;
|
||||
|
||||
/**
|
||||
* Access to {@code server.properties} for transformations moving options between it and the Paper configs.
|
||||
*/
|
||||
public interface ServerPropertiesAccess {
|
||||
|
||||
DedicatedServerProperties current();
|
||||
|
||||
/**
|
||||
* Edits a copy of the raw properties, then reloads and saves {@code server.properties} from it.
|
||||
*
|
||||
* @param editor edits the raw properties
|
||||
*/
|
||||
void update(Consumer<Properties> editor);
|
||||
|
||||
static ServerPropertiesAccess of(final DedicatedServer server) {
|
||||
return new ServerPropertiesAccess() {
|
||||
@Override
|
||||
public DedicatedServerProperties current() {
|
||||
return server.settings.getProperties();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(final Consumer<Properties> editor) {
|
||||
server.settings.update(config -> {
|
||||
// same as Settings#cloneProperties, used by Settings.MutableValue#update
|
||||
final Properties properties = new Properties();
|
||||
properties.putAll(config.properties);
|
||||
editor.accept(properties);
|
||||
return config.reload(server.registryAccess(), properties, server.options);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+120
-3
@@ -1,15 +1,26 @@
|
||||
package io.papermc.paper.configuration.transformation;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import io.papermc.paper.configuration.Configuration;
|
||||
import io.papermc.paper.configuration.Configurations;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import org.slf4j.Logger;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.NodePath;
|
||||
import org.spongepowered.configurate.transformation.ConfigurationTransformation;
|
||||
import org.spongepowered.configurate.transformation.TransformAction;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static org.spongepowered.configurate.NodePath.path;
|
||||
|
||||
public final class Transformations {
|
||||
private static final Logger LOGGER = LogUtils.getClassLogger();
|
||||
|
||||
private Transformations() {
|
||||
}
|
||||
|
||||
@@ -34,8 +45,114 @@ public final class Transformations {
|
||||
return ConfigurationTransformation.versionedBuilder().versionKey(Configuration.VERSION_FIELD);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface DefaultsAware {
|
||||
void apply(final ConfigurationTransformation.Builder builder, final Configurations.ContextMap contextMap, final ConfigurationNode defaultsNode);
|
||||
public static void pruneEmptySections(final ConfigurationNode node, final String... sections) {
|
||||
for (final String section : sections) {
|
||||
final ConfigurationNode child = node.node(section);
|
||||
if (!child.virtual() && isEffectivelyEmpty(child)) {
|
||||
node.removeChild(section);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isEffectivelyEmpty(final ConfigurationNode node) {
|
||||
if (node.isMap()) {
|
||||
return node.childrenMap().values().stream().allMatch(Transformations::isEffectivelyEmpty);
|
||||
}
|
||||
return node.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes every option a legacy migration left at its original path, so options nothing reads
|
||||
* anymore are not carried into the new config as unknown keys.
|
||||
*
|
||||
* @param legacy the legacy node before migrating
|
||||
* @param migrated the same node after migrating
|
||||
* @param source the legacy file the options came from, for logging
|
||||
* @param keptInPlace options that legitimately keep their path
|
||||
*/
|
||||
public static void removeUnmigrated(final ConfigurationNode legacy, final ConfigurationNode migrated, final String source, final Set<NodePath> keptInPlace) {
|
||||
final List<NodePath> leaves = new ArrayList<>();
|
||||
collectLeaves(legacy, new ArrayList<>(), leaves);
|
||||
final List<String> removed = new ArrayList<>();
|
||||
for (final NodePath leaf : leaves) {
|
||||
if (keptInPlace.contains(leaf) || migrated.node(leaf.array()).virtual()) {
|
||||
continue;
|
||||
}
|
||||
final Object[] path = leaf.array();
|
||||
migrated.node(path).raw(null);
|
||||
removed.add(Arrays.stream(path).map(String::valueOf).collect(Collectors.joining(".")));
|
||||
// drop parents emptied by the removal
|
||||
for (int length = path.length - 1; length > 0; length--) {
|
||||
final ConfigurationNode parent = migrated.node(Arrays.copyOf(path, length));
|
||||
if (!isEffectivelyEmpty(parent)) {
|
||||
break;
|
||||
}
|
||||
parent.raw(null);
|
||||
}
|
||||
}
|
||||
if (!removed.isEmpty()) {
|
||||
LOGGER.warn("Dropped options from {} that are no longer used: {}", source, String.join(", ", removed));
|
||||
}
|
||||
}
|
||||
|
||||
private static void collectLeaves(final ConfigurationNode node, final List<Object> path, final List<NodePath> leaves) {
|
||||
if (node.isMap()) {
|
||||
for (final Map.Entry<Object, ? extends ConfigurationNode> child : node.childrenMap().entrySet()) {
|
||||
path.add(child.getKey());
|
||||
collectLeaves(child.getValue(), path, leaves);
|
||||
path.removeLast();
|
||||
}
|
||||
} else if (!path.isEmpty() && !node.empty()) {
|
||||
leaves.add(NodePath.of(path));
|
||||
}
|
||||
}
|
||||
|
||||
public static ConfigurationTransformation single(final NodePath path, final TransformAction action) {
|
||||
return ConfigurationTransformation.builder().addAction(path, action).build();
|
||||
}
|
||||
|
||||
public static TransformAction setRaw(final Object value) {
|
||||
return (path, node) -> {
|
||||
node.raw(value);
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
public static NodePath prefix(final String prefix, final String... other) {
|
||||
final List<String> list = new ArrayList<>(other.length + 1);
|
||||
list.add(prefix);
|
||||
list.addAll(Arrays.asList(other));
|
||||
return NodePath.of(list);
|
||||
}
|
||||
|
||||
public static TransformAction move(final NodePath to) {
|
||||
return (path, node) -> to.array();
|
||||
}
|
||||
|
||||
// replaces the last path element with all the arguments
|
||||
public static TransformAction renameDeep(final String first, final String... other) {
|
||||
return (path, node) -> {
|
||||
final Object[] current = path.array();
|
||||
final Object[] newPath = new Object[current.length + other.length];
|
||||
System.arraycopy(current, 0, newPath, 0, current.length - 1);
|
||||
newPath[current.length - 1] = first;
|
||||
System.arraycopy(other, 0, newPath, current.length, other.length);
|
||||
return newPath;
|
||||
};
|
||||
}
|
||||
|
||||
public static TransformAction move(final TransformAction action, final NodePath newPath) {
|
||||
return (path, node) -> {
|
||||
action.visitPath(path, node);
|
||||
return newPath.array();
|
||||
};
|
||||
}
|
||||
|
||||
public static TransformAction moveParent(final String... parents) {
|
||||
return (path, value) -> {
|
||||
checkState(path.size() > 0, "can't move root");
|
||||
final NodePath newPath = path((Object[]) parents);
|
||||
return newPath.withAppendedChild(path.get(path.size() - 1)).array();
|
||||
};
|
||||
}
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
package io.papermc.paper.configuration.transformation.global;
|
||||
|
||||
import io.papermc.paper.configuration.transformation.Transformations;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import org.spongepowered.configurate.ConfigurateException;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.NodePath;
|
||||
import org.spongepowered.configurate.transformation.ConfigurationTransformation;
|
||||
import org.spongepowered.configurate.transformation.TransformAction;
|
||||
|
||||
import static io.papermc.paper.configuration.transformation.Transformations.move;
|
||||
import static org.spongepowered.configurate.NodePath.path;
|
||||
import static org.spongepowered.configurate.transformation.TransformAction.remove;
|
||||
|
||||
public final class LegacyBukkitConfig {
|
||||
|
||||
private LegacyBukkitConfig() {
|
||||
}
|
||||
|
||||
public static void migrate(final ConfigurationNode node) throws ConfigurateException {
|
||||
final ConfigurationNode legacy = node.copy();
|
||||
toNewFormat().apply(node);
|
||||
// world settings, handled by migrateWorldDefaults
|
||||
node.removeChild("spawn-limits");
|
||||
node.removeChild("ticks-per");
|
||||
Transformations.removeUnmigrated(legacy, node, "bukkit.yml", Set.of());
|
||||
Transformations.pruneEmptySections(node, "settings", "chunk-gc", "worlds");
|
||||
}
|
||||
|
||||
public static ConfigurationTransformation toNewFormat() {
|
||||
return ConfigurationTransformation.builder()
|
||||
.addAction(settings("world-container"), move(path("misc", "world-container")))
|
||||
.addAction(settings("update-folder"), move(path("misc", "update-folder")))
|
||||
.addAction(settings("allow-end"), move(path("misc", "enable-end")))
|
||||
.addAction(settings("permissions-file"), move(path("misc", "permissions-file")))
|
||||
.addAction(settings("connection-throttle"), move(path("misc", "connection-throttle")))
|
||||
.addAction(settings("query-plugins"), move(path("misc", "query-plugins")))
|
||||
.addAction(settings("use-map-color-cache"), move(path("misc", "use-map-color-cache")))
|
||||
.addAction(settings("minimum-api"), move(path("misc", "minimum-api")))
|
||||
.addAction(settings("deprecated-verbose"), (nodePath, value) -> {
|
||||
// was "true"/"false"/"default", it is a WarningState now
|
||||
value.set(switch (value.getString("default").toLowerCase(Locale.ROOT)) {
|
||||
case "true" -> "ON";
|
||||
case "false" -> "OFF";
|
||||
default -> "DEFAULT";
|
||||
});
|
||||
return path("misc", "deprecated-verbose").array();
|
||||
})
|
||||
.addAction(settings("shutdown-message"), move(LegacySpigotConfig.miniMessage(), path("messages", "shutdown")))
|
||||
.addAction(path("chunk-gc", "period-in-ticks"), move(path("chunk-system", "plugin-ticket-timeout")))
|
||||
.addAction(path("ticks-per", "autosave"), move(path("misc", "auto-save-interval")))
|
||||
.addAction(path("worlds", ConfigurationTransformation.WILDCARD_OBJECT, "generator"), intoGenerators())
|
||||
.addAction(path("worlds", ConfigurationTransformation.WILDCARD_OBJECT, "biome-provider"), intoGenerators())
|
||||
// dropped, nothing reads these
|
||||
.addAction(settings("warn-on-overload"), remove())
|
||||
.addAction(settings("plugin-profiling"), remove())
|
||||
.addAction(settings("compatibility"), remove()) // CraftServer#loadCompatibilities has been a no-op for years
|
||||
.build();
|
||||
}
|
||||
|
||||
private static NodePath settings(final String key) {
|
||||
return path("settings", key);
|
||||
}
|
||||
|
||||
public static ConfigurationTransformation worldDefaultsToNewFormat() {
|
||||
// Keys that belong to the world config; applied to a separate copy and merged into the world defaults
|
||||
return ConfigurationTransformation.builder()
|
||||
.addAction(path("spawn-limits", "monsters"), spawning("spawn-limits", "monster"))
|
||||
.addAction(path("spawn-limits", "animals"), spawning("spawn-limits", "creature"))
|
||||
.addAction(path("spawn-limits", "water-animals"), spawning("spawn-limits", "water_creature"))
|
||||
.addAction(path("spawn-limits", "water-ambient"), spawning("spawn-limits", "water_ambient"))
|
||||
.addAction(path("spawn-limits", "water-underground-creature"), spawning("spawn-limits", "underground_water_creature"))
|
||||
.addAction(path("spawn-limits", "axolotls"), spawning("spawn-limits", "axolotls"))
|
||||
.addAction(path("spawn-limits", "ambient"), spawning("spawn-limits", "ambient"))
|
||||
.addAction(path("ticks-per", "monster-spawns"), spawning("ticks-per-spawn", "monster"))
|
||||
.addAction(path("ticks-per", "animal-spawns"), spawning("ticks-per-spawn", "creature"))
|
||||
.addAction(path("ticks-per", "water-spawns"), spawning("ticks-per-spawn", "water_creature"))
|
||||
.addAction(path("ticks-per", "water-ambient-spawns"), spawning("ticks-per-spawn", "water_ambient"))
|
||||
.addAction(path("ticks-per", "water-underground-creature-spawns"), spawning("ticks-per-spawn", "underground_water_creature"))
|
||||
.addAction(path("ticks-per", "axolotl-spawns"), spawning("ticks-per-spawn", "axolotls"))
|
||||
.addAction(path("ticks-per", "ambient-spawns"), spawning("ticks-per-spawn", "ambient"))
|
||||
.addAction(path("ticks-per", "autosave"), move(path("chunks", "auto-save-interval")))
|
||||
.build();
|
||||
}
|
||||
|
||||
// Expects a copy of the whole bukkit.yml, keeps only what belongs in the world defaults
|
||||
public static void migrateWorldDefaults(final ConfigurationNode node) throws ConfigurateException {
|
||||
worldDefaultsToNewFormat().apply(node);
|
||||
for (final Object key : List.copyOf(node.childrenMap().keySet())) {
|
||||
if (!"entities".equals(key) && !"chunks".equals(key)) {
|
||||
node.removeChild(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static TransformAction spawning(final String section, final String category) {
|
||||
return (nodePath, value) -> {
|
||||
if (value.getInt(0) < 0) { // meant the vanilla value, which is the default now
|
||||
value.raw(null);
|
||||
return null;
|
||||
}
|
||||
return path("entities", "spawning", section, category).array();
|
||||
};
|
||||
}
|
||||
|
||||
private static TransformAction intoGenerators() {
|
||||
return (nodePath, value) -> new Object[]{"world-generators", nodePath.get(1), nodePath.get(2)};
|
||||
}
|
||||
}
|
||||
+8
-3
@@ -171,8 +171,13 @@ public final class LegacyPaperConfig {
|
||||
private static void miniMessageWithTranslatable(final ConfigurationTransformation.Builder builder, final Predicate<String> englishCheck, final String i18nKey, final String... strPath) {
|
||||
miniMessageWithTranslatable(builder, englishCheck, Component.translatable(i18nKey), strPath);
|
||||
}
|
||||
|
||||
private static void miniMessageWithTranslatable(final ConfigurationTransformation.Builder builder, final Predicate<String> englishCheck, final Component component, final String... strPath) {
|
||||
builder.addAction(path((Object[]) strPath), (path, value) -> {
|
||||
builder.addAction(path((Object[]) strPath), miniMessageWithTranslatable(englishCheck, component));
|
||||
}
|
||||
|
||||
static TransformAction miniMessageWithTranslatable(final Predicate<? super String> englishCheck, final Component component) {
|
||||
return (path, value) -> {
|
||||
final Object val = value.raw();
|
||||
if (val != null) {
|
||||
final String strVal = val.toString();
|
||||
@@ -183,7 +188,7 @@ public final class LegacyPaperConfig {
|
||||
}
|
||||
value.set(MiniMessage.miniMessage().serialize(component));
|
||||
return null;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
private static void miniMessage(final ConfigurationTransformation.Builder builder, final String... strPath) {
|
||||
@@ -197,7 +202,7 @@ public final class LegacyPaperConfig {
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation") // valid use to convert legacy string to mini-message in legacy migration
|
||||
private static String miniMessage(final String input) {
|
||||
static String miniMessage(final String input) {
|
||||
return MiniMessage.miniMessage().serialize(LegacyComponentSerializer.legacySection().deserialize(ChatColor.translateAlternateColorCodes('&', input)));
|
||||
}
|
||||
|
||||
|
||||
+211
@@ -0,0 +1,211 @@
|
||||
package io.papermc.paper.configuration.transformation.global;
|
||||
|
||||
import io.papermc.paper.configuration.Configuration;
|
||||
import io.papermc.paper.configuration.transformation.Transformations;
|
||||
import org.spongepowered.configurate.ConfigurateException;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.NodePath;
|
||||
import org.spongepowered.configurate.transformation.ConfigurationTransformation;
|
||||
import org.spongepowered.configurate.transformation.TransformAction;
|
||||
|
||||
import static io.papermc.paper.configuration.transformation.Transformations.move;
|
||||
import static io.papermc.paper.configuration.transformation.Transformations.moveParent;
|
||||
import static io.papermc.paper.configuration.transformation.Transformations.prefix;
|
||||
import static io.papermc.paper.configuration.transformation.Transformations.setRaw;
|
||||
import static io.papermc.paper.configuration.transformation.Transformations.single;
|
||||
import static io.papermc.paper.configuration.transformation.global.LegacyPaperConfig.miniMessageWithTranslatable;
|
||||
import static net.kyori.adventure.text.Component.translatable;
|
||||
import static org.spongepowered.configurate.NodePath.path;
|
||||
import static org.spongepowered.configurate.transformation.TransformAction.remove;
|
||||
import static org.spongepowered.configurate.transformation.TransformAction.rename;
|
||||
import static org.spongepowered.configurate.transformation.TransformAction.set;
|
||||
|
||||
// Transforms spigot.yml non-world settings into the new config format
|
||||
public final class LegacySpigotConfig {
|
||||
|
||||
private static final Set<NodePath> KEPT_IN_PLACE = Set.of(
|
||||
path("players", "disable-saving"),
|
||||
path("advancements", "disable-saving"),
|
||||
path("advancements", "disabled"),
|
||||
path("stats", "disable-saving")
|
||||
);
|
||||
|
||||
private LegacySpigotConfig() {}
|
||||
|
||||
public static void migrate(final ConfigurationNode node) throws ConfigurateException {
|
||||
final ConfigurationNode legacy = node.copy();
|
||||
transformation().apply(node);
|
||||
toNewFormat().apply(node);
|
||||
node.removeChild(Configuration.LEGACY_CONFIG_VERSION_FIELD);
|
||||
Transformations.removeUnmigrated(legacy, node, "spigot.yml", KEPT_IN_PLACE);
|
||||
Transformations.pruneEmptySections(node, "settings", "commands", "messages", "stats", "advancements", "players");
|
||||
}
|
||||
|
||||
public static ConfigurationTransformation transformation() {
|
||||
return ConfigurationTransformation.chain(versioned(), notVersioned());
|
||||
}
|
||||
|
||||
// Version transforms taken directly from the old SpigotConfig class
|
||||
private static ConfigurationTransformation.Versioned versioned() {
|
||||
return ConfigurationTransformation.versionedBuilder()
|
||||
.versionKey(Configuration.LEGACY_CONFIG_VERSION_FIELD)
|
||||
.addVersion(4, single(settings("bungeecord"), setRaw(false)))
|
||||
.addVersion(6, single(commands("tab-complete"), (path, value) -> {
|
||||
if (value.getBoolean(true)) {
|
||||
value.raw(0);
|
||||
} else {
|
||||
value.raw(-1);
|
||||
}
|
||||
return null;
|
||||
}))
|
||||
.addVersion(
|
||||
8,
|
||||
single(path(messages("outdated-client")), setRaw("Outdated client! Please use {0}")),
|
||||
single(path(messages("outdated-server")), setRaw("Outdated server! I'm still on {0}"))
|
||||
)
|
||||
.build();
|
||||
|
||||
}
|
||||
|
||||
// Other non-versioned transforms
|
||||
private static ConfigurationTransformation notVersioned() {
|
||||
return ConfigurationTransformation.builder()
|
||||
.addAction(path("replace-commands"), remove()) // not supported anymore
|
||||
.addAction(commands("replace-commands"), remove()) // not supported anymore
|
||||
.addAction(commands(), (path, value) -> {
|
||||
// adding enabled toggle
|
||||
final List<String> spamExclusions = value.node("spam-exclusions").getList(String.class, List.of("/skill"));
|
||||
final ConfigurationNode enabledNode = value.node("enable-spam-exclusions");
|
||||
if (!enabledNode.virtual()) {
|
||||
return null;
|
||||
}
|
||||
if (spamExclusions.isEmpty()) {
|
||||
enabledNode.set(false);
|
||||
return null;
|
||||
}
|
||||
enabledNode.set(spamExclusions.size() > 1 || !"/skill".equals(spamExclusions.getFirst()));
|
||||
return null;
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
public static ConfigurationTransformation toNewFormat() {
|
||||
return ConfigurationTransformation.versionedBuilder()
|
||||
.versionKey(Configuration.LEGACY_CONFIG_VERSION_FIELD)
|
||||
.addVersion(Configuration.FINAL_LEGACY_SPIGOT_VERSION + 1, newFormatTransformation())
|
||||
.build();
|
||||
}
|
||||
|
||||
private static ConfigurationTransformation newFormatTransformation() {
|
||||
return ConfigurationTransformation.builder()
|
||||
// commands
|
||||
.addAction(commands("log"), move(path("logging", "command-execution")))
|
||||
.addAction(commands("send-namespaced"), rename("send-namespaced-commands"))
|
||||
.addAction(commands("tab-complete"), (path, value) -> {
|
||||
value.raw(value.getInt(0) >= 0); // the int value isn't used anymore, it's only a bool now
|
||||
return commands("tab-completion").array();
|
||||
})
|
||||
.addAction(commands("silent-commandblock-console"), rename("silent-command-block-console"))
|
||||
.addAction(commands("spam-exclusions"), move(path("spam-limiter", "commands", "exclusions")))
|
||||
.addAction(commands("enable-spam-exclusions"), move(path("spam-limiter", "commands", "enabled")))
|
||||
// messages
|
||||
.addAction(
|
||||
messages("whitelist"),
|
||||
move(
|
||||
miniMessageWithTranslatable(
|
||||
Set.of("You are not whitelisted on this server!", "You are not white-listed on this server!")::contains,
|
||||
translatable("multiplayer.disconnect.not_whitelisted")
|
||||
),
|
||||
messages("kick", "whitelist")
|
||||
)
|
||||
)
|
||||
.addAction(messages("unknown-command"), (path, value) -> {
|
||||
// only used to control if there should be a message at all; convert to bool
|
||||
value.raw(!value.getString("").isEmpty());
|
||||
return messages("send-command-parse-failure-message").array();
|
||||
})
|
||||
.addAction(messages("server-full"), move(
|
||||
miniMessageWithTranslatable("The server is full!"::equals, translatable("multiplayer.disconnect.server_full")),
|
||||
messages("kick", "server-full")
|
||||
))
|
||||
.addAction(
|
||||
messages("outdated-client"),
|
||||
move(
|
||||
versionKickMessage("Outdated client! Please use %s", "multiplayer.disconnect.outdated_client"),
|
||||
messages("kick", "outdated-client")
|
||||
)
|
||||
)
|
||||
.addAction(
|
||||
messages("outdated-server"),
|
||||
move(
|
||||
versionKickMessage("Outdated server! I'm still on %s", "multiplayer.disconnect.outdated_server"),
|
||||
messages("kick", "outdated-server")
|
||||
)
|
||||
)
|
||||
.addAction(messages("restart"), move(miniMessage(), messages("kick", "restart")))
|
||||
// players.disable-saving
|
||||
// advancements.disable-saving
|
||||
// advancements.disabled
|
||||
// stats.disable-saving
|
||||
.addAction(path("stats", "forced-stats"), rename("forced-custom-stat-values"))
|
||||
// settings
|
||||
.addAction(settings("debug"), move(path("logging", "debug")))
|
||||
.addAction(settings("bungeecord"), move(path("proxies", "bungee-cord", "enabled")))
|
||||
.addAction(settings("log-villager-deaths"), move(path("logging", "villager-deaths")))
|
||||
.addAction(settings("log-named-deaths"), move(path("logging", "named-living-entity-deaths")))
|
||||
.addAction(settings("moved-wrongly-threshold"), moveParent("anticheat"))
|
||||
.addAction(settings("moved-too-quickly-multiplier"), moveParent("anticheat"))
|
||||
.addAction(settings("timeout-time"), move(path("watchdog", "timeout-seconds")))
|
||||
.addAction(settings("restart-on-crash"), moveParent("watchdog"))
|
||||
.addAction(settings("restart-script"), moveParent("watchdog"))
|
||||
.addAction(settings("user-cache-size"), moveParent("players"))
|
||||
.addAction(settings("save-user-cache-on-stop-only"), moveParent("players"))
|
||||
.addAction(settings("netty-threads"), moveParent("misc"))
|
||||
.addAction(settings("sample-count"), moveParent("players"))
|
||||
.addAction(settings("player-shuffle"), move(path("players", "connection-shuffle")))
|
||||
.addAction(settings("attribute", "maxAbsorption", "max"), move(path("attributes", "overrides", "minecraft:max_absorption", "max")))
|
||||
.addAction(settings("attribute", "maxHealth", "max"), move(path("attributes", "overrides", "minecraft:max_health", "max")))
|
||||
.addAction(settings("attribute", "movementSpeed", "max"), move(path("attributes", "overrides", "minecraft:movement_speed", "max")))
|
||||
.addAction(settings("attribute", "attackDamage", "max"), move(path("attributes", "overrides", "minecraft:attack_damage", "max")))
|
||||
.build();
|
||||
}
|
||||
|
||||
private static NodePath settings(final String... other) {
|
||||
return prefix("settings", other);
|
||||
}
|
||||
|
||||
private static NodePath commands(final String... other) {
|
||||
return prefix("commands", other);
|
||||
}
|
||||
|
||||
private static NodePath messages(final String... other) {
|
||||
return prefix("messages", other);
|
||||
}
|
||||
|
||||
// spigot used {0} for the server version, the kick message substitutes it into %s
|
||||
private static TransformAction versionKickMessage(final String englishDefault, final String translationKey) {
|
||||
final TransformAction convert = miniMessageWithTranslatable(
|
||||
Set.of(englishDefault, "Incompatible client! Please use %s")::contains,
|
||||
translatable(translationKey)
|
||||
);
|
||||
return (path, value) -> {
|
||||
final String message = value.getString();
|
||||
if (message != null) {
|
||||
value.raw(message.replaceAll("\\{\\d+}", "%s"));
|
||||
}
|
||||
return convert.visitPath(path, value);
|
||||
};
|
||||
}
|
||||
|
||||
static TransformAction miniMessage() {
|
||||
return (path, value) -> {
|
||||
final Object val = value.raw();
|
||||
if (val != null) {
|
||||
value.set(LegacyPaperConfig.miniMessage(val.toString()));
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
}
|
||||
+25
-24
@@ -1,44 +1,45 @@
|
||||
package io.papermc.paper.configuration.transformation.global.versioned;
|
||||
|
||||
import java.util.Properties;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.dedicated.DedicatedServer;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.spongepowered.configurate.ConfigurateException;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import io.papermc.paper.configuration.Configurations;
|
||||
import io.papermc.paper.configuration.Configurations.ContextKey;
|
||||
import io.papermc.paper.configuration.Configurations.ContextMap;
|
||||
import io.papermc.paper.configuration.transformation.ContextualTransformation;
|
||||
import io.papermc.paper.configuration.transformation.ServerPropertiesAccess;
|
||||
import java.util.Set;
|
||||
import org.spongepowered.configurate.NodePath;
|
||||
import org.spongepowered.configurate.transformation.ConfigurationTransformation;
|
||||
import org.spongepowered.configurate.transformation.TransformAction;
|
||||
|
||||
import static org.spongepowered.configurate.NodePath.path;
|
||||
|
||||
public class V29_LogIPs implements TransformAction {
|
||||
public final class V29_LogIPs implements ContextualTransformation {
|
||||
|
||||
public static final V29_LogIPs INSTANCE = new V29_LogIPs();
|
||||
private static final int VERSION = 29;
|
||||
private static final NodePath PATH = path("logging", "log-player-ip-addresses");
|
||||
private static final V29_LogIPs INSTANCE = new V29_LogIPs();
|
||||
|
||||
private V29_LogIPs() {
|
||||
}
|
||||
|
||||
public static void apply(final ConfigurationTransformation.VersionedBuilder builder) {
|
||||
builder.addVersion(VERSION, ConfigurationTransformation.builder().addAction(PATH, INSTANCE).build());
|
||||
@Override
|
||||
public int version() {
|
||||
return VERSION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object @Nullable [] visitPath(final NodePath path, final ConfigurationNode value) throws ConfigurateException {
|
||||
final DedicatedServer server = ((DedicatedServer) MinecraftServer.getServer());
|
||||
|
||||
final boolean val = value.getBoolean(server.settings.getProperties().logIPs);
|
||||
server.settings.update((config) -> {
|
||||
final Properties newProps = new Properties(config.properties);
|
||||
newProps.setProperty("log-ips", String.valueOf(val));
|
||||
return config.reload(server.registryAccess(), newProps, server.options);
|
||||
});
|
||||
|
||||
value.raw(null);
|
||||
|
||||
return null;
|
||||
public Set<ContextKey<?>> requiredContext() {
|
||||
return Set.of(Configurations.SERVER_PROPERTIES);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigurationTransformation create(final ContextMap context) {
|
||||
final ServerPropertiesAccess serverProperties = context.require(Configurations.SERVER_PROPERTIES);
|
||||
return ConfigurationTransformation.builder().addAction(PATH, (path, value) -> {
|
||||
final boolean val = value.getBoolean(serverProperties.current().logIPs);
|
||||
serverProperties.update(properties -> properties.setProperty("log-ips", String.valueOf(val)));
|
||||
|
||||
value.raw(null);
|
||||
|
||||
return null;
|
||||
}).build();
|
||||
}
|
||||
}
|
||||
+12
-9
@@ -5,24 +5,23 @@ import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import io.papermc.paper.configuration.Configurations.ContextMap;
|
||||
import io.papermc.paper.configuration.transformation.ContextualTransformation;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.NodePath;
|
||||
import org.spongepowered.configurate.transformation.ConfigurationTransformation;
|
||||
import org.spongepowered.configurate.transformation.TransformAction;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.spongepowered.configurate.NodePath.path;
|
||||
|
||||
public class V30_PacketIds implements TransformAction {
|
||||
public final class V30_PacketIds implements ContextualTransformation {
|
||||
|
||||
public static final V30_PacketIds INSTANCE = new V30_PacketIds();
|
||||
private static final int VERSION = 30;
|
||||
@@ -48,16 +47,20 @@ public class V30_PacketIds implements TransformAction {
|
||||
MOJANG_TO_ID = builder2.build();
|
||||
}
|
||||
|
||||
public static void apply(final ConfigurationTransformation.VersionedBuilder builder) {
|
||||
builder.addVersion(VERSION, ConfigurationTransformation.builder().addAction(path("packet-limiter", "overrides", ConfigurationTransformation.WILDCARD_OBJECT), INSTANCE).build());
|
||||
}
|
||||
|
||||
private V30_PacketIds() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int version() {
|
||||
return VERSION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object @Nullable [] visitPath(final NodePath path, final ConfigurationNode value) {
|
||||
public ConfigurationTransformation create(final ContextMap context) {
|
||||
return ConfigurationTransformation.builder().addAction(path("packet-limiter", "overrides", ConfigurationTransformation.WILDCARD_OBJECT), V30_PacketIds::renamePacket).build();
|
||||
}
|
||||
|
||||
private static Object @Nullable [] renamePacket(final NodePath path, final ConfigurationNode value) {
|
||||
final String oldClassName = path.get(path.size() - 1).toString();
|
||||
if (MOJANG_TO_ID.containsKey(oldClassName)) {
|
||||
return path.with(path.size() - 1, MOJANG_TO_ID.get(oldClassName).toString()).array();
|
||||
|
||||
+28
-19
@@ -1,33 +1,42 @@
|
||||
package io.papermc.paper.configuration.transformation.global.versioned;
|
||||
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.dedicated.DedicatedServer;
|
||||
import org.spongepowered.configurate.ConfigurateException;
|
||||
import io.papermc.paper.configuration.Configurations;
|
||||
import io.papermc.paper.configuration.Configurations.ContextKey;
|
||||
import io.papermc.paper.configuration.Configurations.ContextMap;
|
||||
import io.papermc.paper.configuration.transformation.ContextualTransformation;
|
||||
import io.papermc.paper.configuration.transformation.ServerPropertiesAccess;
|
||||
import java.util.Set;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.transformation.ConfigurationTransformation;
|
||||
|
||||
import java.util.Properties;
|
||||
public final class V31_AllowNetherPropertiesToConfig implements ContextualTransformation {
|
||||
|
||||
public class V31_AllowNetherPropertiesToConfig implements ConfigurationTransformation {
|
||||
public static final V31_AllowNetherPropertiesToConfig INSTANCE = new V31_AllowNetherPropertiesToConfig();
|
||||
private static final int VERSION = 31;
|
||||
|
||||
public static void apply(final ConfigurationTransformation.VersionedBuilder builder) {
|
||||
builder.addVersion(VERSION, INSTANCE);
|
||||
private V31_AllowNetherPropertiesToConfig() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(final ConfigurationNode root) throws ConfigurateException {
|
||||
final DedicatedServer server = ((DedicatedServer) MinecraftServer.getServer());
|
||||
final String raw = server.settings.getProperties().properties.getProperty("allow-nether");
|
||||
if (raw != null) {
|
||||
final ConfigurationNode node = root.node("misc", "enable-nether");
|
||||
node.set(raw.equals("true"));
|
||||
server.settings.update((config) -> {
|
||||
final Properties newProps = new Properties(config.properties);
|
||||
newProps.remove("allow-nether");
|
||||
return config.reload(server.registryAccess(), newProps, server.options);
|
||||
});
|
||||
}
|
||||
public int version() {
|
||||
return VERSION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ContextKey<?>> requiredContext() {
|
||||
return Set.of(Configurations.SERVER_PROPERTIES);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigurationTransformation create(final ContextMap context) {
|
||||
final ServerPropertiesAccess serverProperties = context.require(Configurations.SERVER_PROPERTIES);
|
||||
return root -> {
|
||||
final String raw = serverProperties.current().properties.getProperty("allow-nether");
|
||||
if (raw != null) {
|
||||
final ConfigurationNode node = root.node("misc", "enable-nether");
|
||||
node.set(raw.equals("true"));
|
||||
serverProperties.update(properties -> properties.remove("allow-nether"));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package io.papermc.paper.configuration.transformation.global.versioned;
|
||||
|
||||
import io.papermc.paper.configuration.Configurations.ContextMap;
|
||||
import io.papermc.paper.configuration.transformation.ContextualTransformation;
|
||||
import org.spongepowered.configurate.transformation.ConfigurationTransformation;
|
||||
import org.spongepowered.configurate.transformation.MoveStrategy;
|
||||
|
||||
import static io.papermc.paper.configuration.transformation.Transformations.move;
|
||||
import static org.spongepowered.configurate.NodePath.path;
|
||||
|
||||
public final class V32_SpigotConfigCleanup implements ContextualTransformation {
|
||||
|
||||
public static final V32_SpigotConfigCleanup INSTANCE = new V32_SpigotConfigCleanup();
|
||||
private static final int VERSION = 32;
|
||||
|
||||
private V32_SpigotConfigCleanup() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int version() {
|
||||
return VERSION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigurationTransformation create(final ContextMap context) {
|
||||
return ConfigurationTransformation.builder()
|
||||
.moveStrategy(MoveStrategy.MERGE)
|
||||
.addAction(path("player-auto-save"), move(path("players", "auto-save")))
|
||||
.addAction(path("misc", "strict-advancement-dimension-check"), move(path("advancements", "strict-dimension-check")))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
package io.papermc.paper.configuration.transformation.world;
|
||||
|
||||
import io.papermc.paper.configuration.Configuration;
|
||||
import io.papermc.paper.configuration.transformation.Transformations;
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import org.spongepowered.configurate.ConfigurateException;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.NodePath;
|
||||
import org.spongepowered.configurate.transformation.ConfigurationTransformation;
|
||||
|
||||
import static io.papermc.paper.configuration.transformation.Transformations.move;
|
||||
import static org.spongepowered.configurate.NodePath.path;
|
||||
import static org.spongepowered.configurate.transformation.TransformAction.remove;
|
||||
|
||||
public final class LegacySpigotWorldConfig {
|
||||
|
||||
// hunger.* keeps its paths
|
||||
private static final String[] HUNGER_KEYS = {
|
||||
"jump-walk-exhaustion", "jump-sprint-exhaustion", "combat-exhaustion", "regen-exhaustion",
|
||||
"swim-multiplier", "sprint-multiplier", "other-multiplier"
|
||||
};
|
||||
|
||||
private LegacySpigotWorldConfig() {
|
||||
}
|
||||
|
||||
public static void migrate(final ConfigurationNode node, final String source) throws ConfigurateException {
|
||||
final ConfigurationNode legacy = node.copy();
|
||||
toNewFormat().apply(node);
|
||||
node.removeChild(Configuration.LEGACY_CONFIG_VERSION_FIELD);
|
||||
final Set<NodePath> keptInPlace = Arrays.stream(HUNGER_KEYS).map(key -> path("hunger", key)).collect(Collectors.toSet());
|
||||
Transformations.removeUnmigrated(legacy, node, source, keptInPlace);
|
||||
pruneEmptyLegacySections(node);
|
||||
}
|
||||
|
||||
public static ConfigurationTransformation toNewFormat() {
|
||||
return ConfigurationTransformation.versionedBuilder()
|
||||
.versionKey(Configuration.LEGACY_CONFIG_VERSION_FIELD)
|
||||
.addVersion(Configuration.FINAL_LEGACY_SPIGOT_VERSION + 1, newFormatTransformation())
|
||||
.build();
|
||||
}
|
||||
|
||||
private static ConfigurationTransformation newFormatTransformation() {
|
||||
final ConfigurationTransformation.Builder builder = ConfigurationTransformation.builder()
|
||||
// chunks
|
||||
.addAction(path("view-distance"), move(path("chunks", "view-distance")))
|
||||
.addAction(path("simulation-distance"), move(path("chunks", "simulation-distance")))
|
||||
.addAction(path("unload-frozen-chunks"), move(path("chunks", "unload-frozen-chunks")))
|
||||
// entities: spawning
|
||||
.addAction(path("mob-spawn-range"), move(entities("spawning", "mob-spawn-range")))
|
||||
.addAction(path("nerf-spawner-mobs"), move(entities("spawning", "spawner-mobs", "nerf")))
|
||||
.addAction(path("enable-zombie-pigmen-portal-spawns"), move(entities("spawning", "portal-zombified-piglins", "enabled")))
|
||||
.addAction(path("item-despawn-rate"), move(entities("spawning", "item-despawn-rate")))
|
||||
.addAction(path("arrow-despawn-rate"), move(entities("spawning", "arrow-despawn-rate")))
|
||||
.addAction(path("trident-despawn-rate"), move(entities("spawning", "trident-despawn-rate")))
|
||||
// entities: behavior
|
||||
.addAction(path("zombie-aggressive-towards-villager"), move(entities("behavior", "zombie-aggressive-towards-villager")))
|
||||
.addAction(path("merge-radius", "item"), move(entities("behavior", "merge-radius", "item")))
|
||||
.addAction(path("merge-radius", "exp"), move(entities("behavior", "merge-radius", "experience")))
|
||||
// entities: tracking range (spigot uses plurals, the paper section is singular)
|
||||
.addAction(path("entity-tracking-range", "players"), move(entities("tracking-range", "player")))
|
||||
.addAction(path("entity-tracking-range", "animals"), move(entities("tracking-range", "animal")))
|
||||
.addAction(path("entity-tracking-range", "monsters"), move(entities("tracking-range", "monster")))
|
||||
.addAction(path("entity-tracking-range", "misc"), move(entities("tracking-range", "misc")))
|
||||
.addAction(path("entity-tracking-range", "display"), move(entities("tracking-range", "display")))
|
||||
.addAction(path("entity-tracking-range", "other"), move(entities("tracking-range", "other")))
|
||||
// collisions
|
||||
.addAction(path("max-entity-collisions"), move(path("collisions", "max-entity-collisions")))
|
||||
// environment
|
||||
.addAction(path("thunder-chance"), move(path("environment", "thunder-chance")))
|
||||
// hopper
|
||||
.addAction(path("hopper-amount"), move(path("hopper", "amount")))
|
||||
.addAction(path("hopper-can-load-chunks"), move(path("hopper", "can-load-chunks")))
|
||||
// tick rates
|
||||
.addAction(path("ticks-per", "hopper-transfer"), move(path("tick-rates", "hopper-transfer")))
|
||||
.addAction(path("ticks-per", "hopper-check"), move(path("tick-rates", "hopper-check")))
|
||||
.addAction(path("hanging-tick-frequency"), move(path("tick-rates", "hanging-tick-frequency")))
|
||||
.addAction(path("max-tick-time"), remove())
|
||||
// fixes
|
||||
.addAction(path("max-tnt-per-tick"), move(path("fixes", "max-tnt-per-tick")))
|
||||
// misc
|
||||
.addAction(path("dragon-death-sound-radius"), move(path("misc", "sound-radius", "dragon-death")))
|
||||
.addAction(path("wither-spawn-sound-radius"), move(path("misc", "sound-radius", "wither-spawn")))
|
||||
.addAction(path("end-portal-sound-radius"), move(path("misc", "sound-radius", "end-portal")))
|
||||
// only ever controlled SpigotWorldConfig's own startup logging, which no longer exists
|
||||
.addAction(path("verbose"), remove())
|
||||
.addAction(path("below-zero-generation-in-existing-chunks"), remove()); // moved to the global config
|
||||
|
||||
// entity-activation-range.* keeps its key names, the section is just renamed
|
||||
for (final String key : new String[]{
|
||||
"animals", "monsters", "raiders", "misc", "water", "villagers", "flying-monsters",
|
||||
"villagers-work-immunity-after", "villagers-work-immunity-for", "villagers-active-for-panic",
|
||||
"tick-inactive-villagers", "ignore-spectators"
|
||||
}) {
|
||||
builder.addAction(path("entity-activation-range", key), move(entities("activation-range", key)));
|
||||
}
|
||||
for (final String category : new String[]{"animals", "monsters", "villagers", "flying-monsters"}) {
|
||||
for (final String suffix : new String[]{"max-per-tick", "every", "for"}) {
|
||||
final String key = category + "-" + suffix;
|
||||
builder.addAction(
|
||||
path("entity-activation-range", "wake-up-inactive", key),
|
||||
move(entities("activation-range", "wake-up-inactive", key))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// growth.<plant>-modifier -> growth-modifiers.<plant>, dropping the suffix
|
||||
for (final String[] pair : new String[][]{
|
||||
{"cactus", "cactus"}, {"cane", "cane"}, {"melon", "melon"}, {"mushroom", "mushroom"},
|
||||
{"pumpkin", "pumpkin"}, {"sapling", "sapling"}, {"beetroot", "beetroot"}, {"carrot", "carrot"},
|
||||
{"potato", "potato"}, {"torchflower", "torch-flower"}, {"wheat", "wheat"}, {"netherwart", "nether-wart"},
|
||||
{"vine", "vine"}, {"cocoa", "cocoa"}, {"bamboo", "bamboo"}, {"sweetberry", "sweet-berry"},
|
||||
{"kelp", "kelp"}, {"twistingvines", "twisting-vines"}, {"weepingvines", "weeping-vines"},
|
||||
{"cavevines", "cave-vines"}, {"glowberry", "glow-berry"}, {"pitcherplant", "pitcher-plant"}
|
||||
}) {
|
||||
builder.addAction(path("growth", pair[0] + "-modifier"), move(path("growth-modifiers", pair[1])));
|
||||
}
|
||||
|
||||
// seed-<feature> -> seeds.<feature>
|
||||
for (final String[] pair : new String[][]{
|
||||
{"village", "village"}, {"desert", "desert"}, {"igloo", "igloo"}, {"jungle", "jungle"},
|
||||
{"swamp", "swamp"}, {"monument", "monument"}, {"shipwreck", "shipwreck"}, {"ocean", "ocean"},
|
||||
{"outpost", "outpost"}, {"endcity", "end-city"}, {"slime", "slime"}, {"nether", "nether"},
|
||||
{"mansion", "mansion"}, {"fossil", "fossil"}, {"portal", "portal"}, {"ancientcity", "ancient-city"},
|
||||
{"trailruins", "trail-ruins"}, {"trialchambers", "trial-chambers"}, {"abandonedcamp", "abandoned-camp"},
|
||||
{"buriedtreasure", "buried-treasure"}, {"mineshaft", "mineshaft"}, {"stronghold", "stronghold"}
|
||||
}) {
|
||||
builder.addAction(path("seed-" + pair[0]), move(path("seeds", pair[1])));
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public static void pruneEmptyLegacySections(final ConfigurationNode node) {
|
||||
for (final String legacy : new String[]{
|
||||
"entity-activation-range", "entity-tracking-range", "growth", "merge-radius", "ticks-per", "max-tick-time"
|
||||
}) {
|
||||
final ConfigurationNode child = node.node(legacy);
|
||||
if (!child.virtual() && isEffectivelyEmpty(child)) {
|
||||
node.removeChild(legacy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isEffectivelyEmpty(final ConfigurationNode node) {
|
||||
if (node.isMap()) {
|
||||
return node.childrenMap().values().stream().allMatch(LegacySpigotWorldConfig::isEffectivelyEmpty);
|
||||
}
|
||||
return node.empty();
|
||||
}
|
||||
|
||||
private static NodePath entities(final String... other) {
|
||||
return Transformations.prefix("entities", other);
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package io.papermc.paper.configuration.transformation.world.versioned;
|
||||
|
||||
import org.spongepowered.configurate.transformation.ConfigurationTransformation;
|
||||
import org.spongepowered.configurate.transformation.TransformAction;
|
||||
|
||||
import static io.papermc.paper.configuration.transformation.Transformations.move;
|
||||
import static org.spongepowered.configurate.NodePath.path;
|
||||
|
||||
/**
|
||||
* The spawn limits, spawn intervals and save interval used to defer to {@code bukkit.yml} via {@code -1},
|
||||
* or {@code default} for the save interval. Move those into the Paper configs directly.
|
||||
* <p>
|
||||
* The nerf options join the spigot spawn toggles they belong to.
|
||||
*/
|
||||
public final class V32_WorldConfigCleanup {
|
||||
|
||||
private static final int VERSION = 32;
|
||||
|
||||
public static void apply(final ConfigurationTransformation.VersionedBuilder builder) {
|
||||
final ConfigurationTransformation.Builder transformation = ConfigurationTransformation.builder()
|
||||
.addAction(path("chunks", "auto-save-interval"), (nodePath, value) -> {
|
||||
if ("default".equals(value.getString())) {
|
||||
value.raw(null);
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.addAction(path("entities", "behavior", "spawner-nerfed-mobs-should-jump"), move(path("entities", "spawning", "spawner-mobs", "nerfed-should-jump")))
|
||||
.addAction(path("entities", "behavior", "nerf-pigmen-from-nether-portals"), move(path("entities", "spawning", "portal-zombified-piglins", "nerf")));
|
||||
for (final String section : new String[]{"spawn-limits", "ticks-per-spawn"}) {
|
||||
transformation.addAction(
|
||||
path("entities", "spawning", section, ConfigurationTransformation.WILDCARD_OBJECT),
|
||||
dropNegativeSentinel()
|
||||
);
|
||||
}
|
||||
builder.addVersion(VERSION, transformation.build());
|
||||
}
|
||||
|
||||
private static TransformAction dropNegativeSentinel() {
|
||||
return (nodePath, value) -> {
|
||||
if (value.getInt(0) < 0) {
|
||||
value.raw(null);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
private V32_WorldConfigCleanup() {
|
||||
}
|
||||
}
|
||||
+4
-5
@@ -1,6 +1,5 @@
|
||||
package io.papermc.paper.configuration.type.fallback;
|
||||
|
||||
import org.spigotmc.SpigotWorldConfig;
|
||||
import org.spongepowered.configurate.serialize.SerializationException;
|
||||
|
||||
import java.util.Map;
|
||||
@@ -24,15 +23,15 @@ public class ArrowDespawnRate extends FallbackValue.Int {
|
||||
|
||||
@Override
|
||||
public Set<ContextKey<?>> required() {
|
||||
return Set.of(FallbackValue.SPIGOT_WORLD_CONFIG);
|
||||
return Set.of(FallbackValue.WORLD_CONFIG);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int fallback() {
|
||||
return this.get(FallbackValue.SPIGOT_WORLD_CONFIG).arrowDespawnRate;
|
||||
return this.get(FallbackValue.WORLD_CONFIG).entities.spawning.arrowDespawnRate;
|
||||
}
|
||||
|
||||
public static ArrowDespawnRate def(SpigotWorldConfig spigotConfig) {
|
||||
return new ArrowDespawnRate(FallbackValue.SPIGOT_WORLD_CONFIG.singleton(spigotConfig));
|
||||
public static ArrowDespawnRate def(io.papermc.paper.configuration.WorldConfiguration worldConfig) {
|
||||
return new ArrowDespawnRate(FallbackValue.WORLD_CONFIG.singleton(worldConfig));
|
||||
}
|
||||
}
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
package io.papermc.paper.configuration.type.fallback;
|
||||
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import org.spongepowered.configurate.serialize.SerializationException;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.OptionalInt;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class AutosavePeriod extends FallbackValue.Int {
|
||||
|
||||
AutosavePeriod(Map<ContextKey<?>, Object> contextMap, Object value) throws SerializationException {
|
||||
super(contextMap, fromObject(value));
|
||||
}
|
||||
|
||||
private AutosavePeriod(Map<ContextKey<?>, Object> contextMap) {
|
||||
super(contextMap, OptionalInt.empty());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected OptionalInt process(int value) {
|
||||
return Util.negToDef(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<ContextKey<?>> required() {
|
||||
return Set.of(FallbackValue.MINECRAFT_SERVER);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int fallback() {
|
||||
return this.get(FallbackValue.MINECRAFT_SERVER).get().autosavePeriod;
|
||||
}
|
||||
|
||||
public static AutosavePeriod def() {
|
||||
return new AutosavePeriod(FallbackValue.MINECRAFT_SERVER.singleton(MinecraftServer::getServer));
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
package io.papermc.paper.configuration.type.fallback;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.papermc.paper.configuration.WorldConfiguration;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
import org.spigotmc.SpigotWorldConfig;
|
||||
import org.spongepowered.configurate.serialize.SerializationException;
|
||||
|
||||
import java.util.Map;
|
||||
@@ -16,7 +16,7 @@ import java.util.function.Supplier;
|
||||
public sealed abstract class FallbackValue permits FallbackValue.Int {
|
||||
|
||||
private static final String DEFAULT_VALUE = "default";
|
||||
static final ContextKey<SpigotWorldConfig> SPIGOT_WORLD_CONFIG = new ContextKey<>("SpigotWorldConfig");
|
||||
static final ContextKey<WorldConfiguration> WORLD_CONFIG = new ContextKey<>("WorldConfiguration");
|
||||
static final ContextKey<Supplier<MinecraftServer>> MINECRAFT_SERVER = new ContextKey<>("MinecraftServer");
|
||||
|
||||
private final Map<ContextKey<?>, Object> contextMap;
|
||||
|
||||
+3
-4
@@ -1,12 +1,12 @@
|
||||
package io.papermc.paper.configuration.type.fallback;
|
||||
|
||||
import io.papermc.paper.configuration.WorldConfiguration;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import org.spigotmc.SpigotWorldConfig;
|
||||
import org.spongepowered.configurate.serialize.ScalarSerializer;
|
||||
import org.spongepowered.configurate.serialize.SerializationException;
|
||||
|
||||
@@ -18,7 +18,6 @@ public class FallbackValueSerializer extends ScalarSerializer<FallbackValue> {
|
||||
|
||||
static {
|
||||
REGISTRY.put(ArrowDespawnRate.class, ArrowDespawnRate::new);
|
||||
REGISTRY.put(AutosavePeriod.class, AutosavePeriod::new);
|
||||
}
|
||||
|
||||
FallbackValueSerializer(Map<FallbackValue.ContextKey<?>, Object> contextMap) {
|
||||
@@ -47,7 +46,7 @@ public class FallbackValueSerializer extends ScalarSerializer<FallbackValue> {
|
||||
return item.serialize();
|
||||
}
|
||||
|
||||
public static FallbackValueSerializer create(SpigotWorldConfig config, Supplier<MinecraftServer> server) {
|
||||
return new FallbackValueSerializer(Map.of(FallbackValue.SPIGOT_WORLD_CONFIG, config, FallbackValue.MINECRAFT_SERVER, server));
|
||||
public static FallbackValueSerializer create(WorldConfiguration config, Supplier<MinecraftServer> server) {
|
||||
return new FallbackValueSerializer(Map.of(FallbackValue.WORLD_CONFIG, config, FallbackValue.MINECRAFT_SERVER, server));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package io.papermc.paper.configuration.type.number;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import java.lang.reflect.AnnotatedType;
|
||||
import java.util.OptionalLong;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import org.slf4j.Logger;
|
||||
import org.spongepowered.configurate.serialize.ScalarSerializer;
|
||||
|
||||
public interface LongOr {
|
||||
|
||||
Logger LOGGER = LogUtils.getClassLogger();
|
||||
|
||||
default long or(final long fallback) {
|
||||
return this.value().orElse(fallback);
|
||||
}
|
||||
|
||||
OptionalLong value();
|
||||
|
||||
default boolean isDefined() {
|
||||
return this.value().isPresent();
|
||||
}
|
||||
|
||||
default long longValue() {
|
||||
return this.value().orElseThrow();
|
||||
}
|
||||
|
||||
record Default(OptionalLong value) implements LongOr {
|
||||
public static final Default USE_DEFAULT = new Default(OptionalLong.empty());
|
||||
private static final String DEFAULT_VALUE = "default";
|
||||
public static final ScalarSerializer<Default> SERIALIZER = new Serializer<>(Default.class, Default::new, DEFAULT_VALUE, USE_DEFAULT);
|
||||
}
|
||||
|
||||
final class Serializer<T extends LongOr> extends OptionalNumSerializer<T, OptionalLong> {
|
||||
|
||||
private Serializer(final Class<T> classOfT, final Function<OptionalLong, T> factory, final String emptySerializedValue, final T emptyValue) {
|
||||
super(classOfT, emptySerializedValue, emptyValue, OptionalLong::empty, OptionalLong::isEmpty, factory, long.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected OptionalLong full(final String value) {
|
||||
return OptionalLong.of(Long.parseLong(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected OptionalLong full(final Number num) {
|
||||
if (num.longValue() != num.doubleValue()) {
|
||||
LOGGER.error("{} cannot be converted to a long without losing information", num);
|
||||
}
|
||||
return OptionalLong.of(num.longValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean belowZero(final OptionalLong value) {
|
||||
Preconditions.checkArgument(value.isPresent());
|
||||
return value.getAsLong() < 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object serialize(final AnnotatedType type, final T item, final Predicate<Class<?>> typeSupported) {
|
||||
final OptionalLong value = item.value();
|
||||
if (value.isPresent()) {
|
||||
return value.getAsLong();
|
||||
} else {
|
||||
return this.emptySerializedValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Copied from GeyserMC's Configurate fork:
|
||||
* https://github.com/GeyserMC/Configurate/blob/f6aa72e6099f956e78b638a2a817429007c6898a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/BlankLineStyle.java
|
||||
*
|
||||
* Configurate
|
||||
* Copyright (C) zml and Configurate contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.papermc.paper.configuration.yaml;
|
||||
|
||||
/**
|
||||
* Representation of blank lines in a YAML document.
|
||||
*
|
||||
* @since 4.3.0
|
||||
*/
|
||||
public enum BlankLineStyle {
|
||||
|
||||
/**
|
||||
* Don't do any blank line styling.
|
||||
* This includes seeing blank lines during read as formatting, which will be stripped.
|
||||
* To keep those blank lines, use the {@link #KEEP} style.<br>
|
||||
* <br>
|
||||
* This is the default.<br>
|
||||
* <br>
|
||||
* Example: <pre>
|
||||
* {@code
|
||||
* a: 1
|
||||
* b:
|
||||
* c: 1
|
||||
* e:
|
||||
* - hello
|
||||
* - world
|
||||
* f:
|
||||
* - name: hello
|
||||
* - name: world
|
||||
* g:
|
||||
* h:
|
||||
* - name: hello
|
||||
* - name: world
|
||||
* i: 1
|
||||
* j: 1
|
||||
* }</pre>
|
||||
*
|
||||
* @since 4.3.0
|
||||
*/
|
||||
NONE,
|
||||
/**
|
||||
* Keep the blank lines just like they were read.<br>
|
||||
* <br>
|
||||
* Note that this option, unlike the others,
|
||||
* will also keep read blank lines (and store it in the comment.)
|
||||
* These blank lines are stored using a special character,
|
||||
* so if you write to a different format you may have to remove them manually.
|
||||
*
|
||||
* @since 4.3.0
|
||||
*/
|
||||
KEEP,
|
||||
/**
|
||||
* Add a blank line after a nested node.<br>
|
||||
* <br>
|
||||
* Example: <pre>
|
||||
* {@code
|
||||
* a: 1
|
||||
* b:
|
||||
* c: 1
|
||||
*
|
||||
* e:
|
||||
* - hello
|
||||
* - world
|
||||
*
|
||||
* f:
|
||||
* - name: hello
|
||||
* - name: world
|
||||
*
|
||||
* g:
|
||||
* h:
|
||||
* - name: hello
|
||||
* - name: world
|
||||
*
|
||||
* i: 1
|
||||
*
|
||||
* j: 1
|
||||
* }</pre>
|
||||
*
|
||||
* @since 4.3.0
|
||||
*/
|
||||
AFTER_NESTED,
|
||||
/**
|
||||
* Add a blank after every root child, except the last.<br>
|
||||
* <br>
|
||||
* Example: <pre>
|
||||
* {@code
|
||||
* a: 1
|
||||
*
|
||||
* b:
|
||||
* c: 1
|
||||
*
|
||||
* e:
|
||||
* - hello
|
||||
* - world
|
||||
*
|
||||
* f:
|
||||
* - name: hello
|
||||
* - name: world
|
||||
*
|
||||
* g:
|
||||
* h:
|
||||
* - name: hello
|
||||
* - name: world
|
||||
* i: 1
|
||||
*
|
||||
* j: 1
|
||||
* }</pre>
|
||||
*
|
||||
* @since 4.3.0
|
||||
*/
|
||||
ROOT_CHILDREN,
|
||||
/**
|
||||
* Prepend a blank line before every node with a comment, except from the first child.<br>
|
||||
* <br>
|
||||
* Example: <pre>
|
||||
* {@code
|
||||
* # Hello
|
||||
* a: 1
|
||||
*
|
||||
* # World
|
||||
* b:
|
||||
* # John
|
||||
* c: 1
|
||||
*
|
||||
* # Doe
|
||||
* e:
|
||||
* - hello
|
||||
* - world
|
||||
*
|
||||
* # Jane
|
||||
* f:
|
||||
* - name: hello
|
||||
* - name: world
|
||||
*
|
||||
* # Doe
|
||||
* g:
|
||||
* # Lorum
|
||||
* h:
|
||||
* - name: hello
|
||||
* - name: world
|
||||
*
|
||||
* # Ipsum
|
||||
* i: 1
|
||||
* j: 1
|
||||
* }</pre>
|
||||
*
|
||||
* @since 4.3.0
|
||||
*/
|
||||
BEFORE_COMMENT
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copied from GeyserMC's Configurate fork:
|
||||
* https://github.com/GeyserMC/Configurate/blob/f6aa72e6099f956e78b638a2a817429007c6898a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/NodeStyle.java
|
||||
*
|
||||
* Configurate
|
||||
* Copyright (C) zml and Configurate contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.papermc.paper.configuration.yaml;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.yaml.snakeyaml.DumperOptions;
|
||||
|
||||
/**
|
||||
* Representation of collections and mappings in a YAML document.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public enum NodeStyle {
|
||||
|
||||
/**
|
||||
* Always use <a href="https://yaml.org/spec/1.1/#id903421">block style</a>.
|
||||
*/
|
||||
BLOCK(DumperOptions.FlowStyle.BLOCK),
|
||||
|
||||
/**
|
||||
* Always use <a href="https://yaml.org/spec/1.1/#id902924">flow style</a>.
|
||||
*/
|
||||
FLOW(DumperOptions.FlowStyle.FLOW)
|
||||
;
|
||||
|
||||
private final DumperOptions.FlowStyle snake;
|
||||
|
||||
NodeStyle(final DumperOptions.FlowStyle snake) {
|
||||
this.snake = snake;
|
||||
}
|
||||
|
||||
static DumperOptions.FlowStyle asSnakeYaml(final @Nullable NodeStyle style) {
|
||||
return style == null ? DumperOptions.FlowStyle.AUTO : style.snake;
|
||||
}
|
||||
|
||||
static @Nullable NodeStyle fromSnakeYaml(final DumperOptions.FlowStyle style) {
|
||||
switch (style) {
|
||||
case AUTO:
|
||||
return null;
|
||||
case BLOCK:
|
||||
return BLOCK;
|
||||
case FLOW:
|
||||
return FLOW;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown style " + style);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copied from GeyserMC's Configurate fork:
|
||||
* https://github.com/GeyserMC/Configurate/blob/f6aa72e6099f956e78b638a2a817429007c6898a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/ScalarStyle.java
|
||||
*
|
||||
* Configurate
|
||||
* Copyright (C) zml and Configurate contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.papermc.paper.configuration.yaml;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.yaml.snakeyaml.DumperOptions;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Style that can be used to represent a scalar.
|
||||
*
|
||||
* @since 4.3.0
|
||||
*/
|
||||
public enum ScalarStyle {
|
||||
|
||||
/**
|
||||
* A double-quoted string.
|
||||
*
|
||||
* <pre>"hello world"</pre>
|
||||
*
|
||||
* @since 4.3.0
|
||||
*/
|
||||
DOUBLE_QUOTED(DumperOptions.ScalarStyle.DOUBLE_QUOTED),
|
||||
|
||||
/**
|
||||
* A single-quoted string.
|
||||
*
|
||||
* <pre>'hello world'</pre>
|
||||
*
|
||||
* @since 4.3.0
|
||||
*/
|
||||
SINGLE_QUOTED(DumperOptions.ScalarStyle.SINGLE_QUOTED),
|
||||
|
||||
/**
|
||||
* String without any quotation.
|
||||
*
|
||||
* <p>This may be ambiguous with non-string types.</p>
|
||||
*
|
||||
* @since 4.3.0
|
||||
*/
|
||||
UNQUOTED(DumperOptions.ScalarStyle.PLAIN),
|
||||
|
||||
/**
|
||||
* Folded scalar.
|
||||
*
|
||||
* <pre>{@code
|
||||
* key: >
|
||||
* folded scalar
|
||||
* line breaks collapsed
|
||||
* }</pre>
|
||||
*
|
||||
* @since 4.3.0
|
||||
*/
|
||||
FOLDED(DumperOptions.ScalarStyle.FOLDED),
|
||||
|
||||
/**
|
||||
* Literal scalar.
|
||||
*
|
||||
* <pre>{@code
|
||||
* key: |
|
||||
* literal scalar
|
||||
* line breaks preserved
|
||||
* }</pre>
|
||||
*
|
||||
* @since 4.3.0
|
||||
*/
|
||||
LITERAL(DumperOptions.ScalarStyle.LITERAL)
|
||||
;
|
||||
|
||||
private static final Map<DumperOptions.ScalarStyle, ScalarStyle> BY_SNAKE = new EnumMap<>(DumperOptions.ScalarStyle.class);
|
||||
private final DumperOptions.ScalarStyle snake;
|
||||
|
||||
ScalarStyle(final DumperOptions.ScalarStyle snake) {
|
||||
this.snake = snake;
|
||||
}
|
||||
|
||||
static DumperOptions.ScalarStyle asSnakeYaml(
|
||||
final @Nullable ScalarStyle style,
|
||||
final DumperOptions.@Nullable ScalarStyle fallback
|
||||
) {
|
||||
if (style != null) {
|
||||
return style.snake;
|
||||
} else if (fallback != null) {
|
||||
return fallback;
|
||||
} else {
|
||||
return DumperOptions.ScalarStyle.PLAIN;
|
||||
}
|
||||
}
|
||||
|
||||
static ScalarStyle fromSnakeYaml(final DumperOptions.ScalarStyle style) {
|
||||
return BY_SNAKE.getOrDefault(style, UNQUOTED);
|
||||
}
|
||||
|
||||
static {
|
||||
for (final ScalarStyle style : values()) {
|
||||
BY_SNAKE.put(style.snake, style);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+376
@@ -0,0 +1,376 @@
|
||||
/*
|
||||
* Copied from GeyserMC's Configurate fork:
|
||||
* https://github.com/GeyserMC/Configurate/blob/f6aa72e6099f956e78b638a2a817429007c6898a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConfigurationLoader.java
|
||||
*
|
||||
* Configurate
|
||||
* Copyright (C) zml and Configurate contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.papermc.paper.configuration.yaml;
|
||||
|
||||
import net.kyori.option.Option;
|
||||
import net.kyori.option.OptionSchema;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.spongepowered.configurate.CommentedConfigurationNode;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.ConfigurationOptions;
|
||||
import org.spongepowered.configurate.RepresentationHint;
|
||||
import org.spongepowered.configurate.loader.AbstractConfigurationLoader;
|
||||
import org.spongepowered.configurate.loader.CommentHandler;
|
||||
import org.spongepowered.configurate.loader.CommentHandlers;
|
||||
import org.spongepowered.configurate.util.UnmodifiableCollections;
|
||||
import org.yaml.snakeyaml.DumperOptions;
|
||||
import org.yaml.snakeyaml.LoaderOptions;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
import org.yaml.snakeyaml.constructor.Constructor;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.Writer;
|
||||
import java.math.BigInteger;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.Date;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A loader for YAML-formatted configurations, using the SnakeYAML library for
|
||||
* parsing and generation.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public final class YamlConfigurationLoader extends AbstractConfigurationLoader<CommentedConfigurationNode> {
|
||||
|
||||
/**
|
||||
* YAML native types from <a href="https://yaml.org/type/">YAML 1.1 Global tags</a>.
|
||||
*
|
||||
* <p>using SnakeYaml representation: https://bitbucket.org/snakeyaml/snakeyaml/wiki/Documentation#markdown-header-yaml-tags-and-java-types
|
||||
*/
|
||||
private static final Set<Class<?>> NATIVE_TYPES = UnmodifiableCollections.toSet(
|
||||
Boolean.class, Integer.class, Long.class, BigInteger.class, Double.class, // numeric
|
||||
byte[].class, String.class, Date.class, java.sql.Date.class, Timestamp.class); // complex types
|
||||
|
||||
/**
|
||||
* The YAML scalar style this node should attempt to use.
|
||||
*
|
||||
* <p>If the chosen scalar style would produce syntactically invalid YAML, a
|
||||
* valid one will replace it.</p>
|
||||
*
|
||||
* @since 4.3.0
|
||||
*/
|
||||
public static final RepresentationHint<ScalarStyle> SCALAR_STYLE = RepresentationHint.of("configurate:yaml/scalar_style", ScalarStyle.class);
|
||||
|
||||
/**
|
||||
* The YAML node style to use for collection nodes. A {@code null} value
|
||||
* will instruct the emitter to fall back to the
|
||||
* {@link Builder#nodeStyle()} setting.
|
||||
*
|
||||
* @since 4.3.0
|
||||
*/
|
||||
public static final RepresentationHint<NodeStyle> NODE_STYLE = RepresentationHint.of("configurate:yaml/node_style", NodeStyle.class);
|
||||
|
||||
/**
|
||||
* The amount of blank lines that a specific node should have.
|
||||
* Absent is to inherit the {@link BlankLineStyle} of the document.
|
||||
*
|
||||
* @since 4.3.0
|
||||
*/
|
||||
public static final RepresentationHint<Integer> BLANK_LINE_STYLE_OVERRIDE
|
||||
= RepresentationHint.of("configurate:yaml/blank_line_style_override", Integer.class);
|
||||
|
||||
/**
|
||||
* Creates a new {@link YamlConfigurationLoader} builder.
|
||||
*
|
||||
* @return a new builder
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a {@link YamlConfigurationLoader}.
|
||||
*
|
||||
* <p>This builder supports the following options:</p>
|
||||
* <dl>
|
||||
* <dt><prefix>.yaml.node-style</dt>
|
||||
* <dd>Equivalent to {@link #nodeStyle(NodeStyle)}</dd>
|
||||
* <dt><prefix>.yaml.comments-enabled</dt>
|
||||
* <dd>Equivalent to {@link #commentsEnabled(boolean)}</dd>
|
||||
* <dt><prefix>.yaml.line-length</dt>
|
||||
* <dd>Equivalent to {@link #lineLength(int)}</dd>
|
||||
* </dl>
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public static final class Builder extends AbstractConfigurationLoader.Builder<Builder, YamlConfigurationLoader> {
|
||||
|
||||
private static final OptionSchema.Mutable UNSAFE_SCHEMA = OptionSchema.childSchema(AbstractConfigurationLoader.Builder.SCHEMA);
|
||||
|
||||
/**
|
||||
* A schema of options available to configure the YAML loader.
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public static final OptionSchema SCHEMA = UNSAFE_SCHEMA.frozenView();
|
||||
|
||||
/**
|
||||
* The collection node style to use globally when emitting with
|
||||
* this loader.
|
||||
*
|
||||
* @see #nodeStyle(NodeStyle)
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public static final Option<NodeStyle> NODE_STYLE = UNSAFE_SCHEMA.enumOption("yaml:node_style", NodeStyle.class, null);
|
||||
|
||||
/**
|
||||
* The indent size (in spaces) to use for documents emitted by
|
||||
* the created loader.
|
||||
*
|
||||
* @see #indent(int)
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public static final Option<Integer> INDENT = UNSAFE_SCHEMA.intOption("yaml:indent", 4);
|
||||
|
||||
/**
|
||||
* Whether comments are being read and written.
|
||||
*
|
||||
* @see #commentsEnabled(boolean)
|
||||
* @since 4.3.0
|
||||
*/
|
||||
public static final Option<Boolean> COMMENTS_ENABLED = UNSAFE_SCHEMA.booleanOption("yaml:comments_enabled", true);
|
||||
|
||||
/**
|
||||
* The line length before the scalar representation is split into multiple lines.
|
||||
*
|
||||
* @see #lineLength(int)
|
||||
* @since 4.3.0
|
||||
*/
|
||||
public static final Option<Integer> LINE_LENGTH = UNSAFE_SCHEMA.intOption("yaml:line_length", 150);
|
||||
|
||||
/**
|
||||
* The blank line style for writing YAML documents.
|
||||
*
|
||||
* @see #blankLineStyle(BlankLineStyle)
|
||||
* @since 4.3.0
|
||||
*/
|
||||
public static final Option<BlankLineStyle> BLANK_LINE_STYLE = UNSAFE_SCHEMA.enumOption("yaml:blank_line_style", BlankLineStyle.class, null);
|
||||
|
||||
private final DumperOptions options = new DumperOptions();
|
||||
|
||||
Builder() {
|
||||
this.defaultOptions(o -> o.nativeTypes(NATIVE_TYPES));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected OptionSchema optionSchema() {
|
||||
return SCHEMA;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the level of indentation the resultant loader should use.
|
||||
*
|
||||
* @param indent the indent level
|
||||
* @return this builder (for chaining)
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public Builder indent(final int indent) {
|
||||
this.optionStateBuilder().value(INDENT, indent);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the level of indentation to be used by the resultant loader.
|
||||
*
|
||||
* @return the indent level
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public int indent() {
|
||||
return this.optionState().value(INDENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the node style the built loader should use.
|
||||
*
|
||||
* <dl><dt>Flow</dt>
|
||||
* <dd>the compact, json-like representation.<br>
|
||||
* Example: <code>
|
||||
* {value: [list, of, elements], another: value}
|
||||
* </code></dd>
|
||||
*
|
||||
* <dt>Block</dt>
|
||||
* <dd>expanded, traditional YAML<br>
|
||||
* Example: <code>
|
||||
* value:
|
||||
* - list
|
||||
* - of
|
||||
* - elements
|
||||
* another: value
|
||||
* </code></dd>
|
||||
* </dl>
|
||||
*
|
||||
* <p>A {@code null} value will tell the loader to pick a value
|
||||
* automatically based on the contents of each non-scalar node.</p>
|
||||
*
|
||||
* @param style the node style to use
|
||||
* @return this builder (for chaining)
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public Builder nodeStyle(final @Nullable NodeStyle style) {
|
||||
this.optionStateBuilder().value(NODE_STYLE, style);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the node style to be used by the resultant loader.
|
||||
*
|
||||
* @return the node style
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public @Nullable NodeStyle nodeStyle() {
|
||||
return this.optionState().value(NODE_STYLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the blank line style handling for all written files.
|
||||
*
|
||||
* @param style the style to apply
|
||||
* @return this builder
|
||||
* @see BlankLineStyle
|
||||
* @since 4.3.0
|
||||
*/
|
||||
public Builder blankLineStyle(final @Nullable BlankLineStyle style) {
|
||||
this.optionStateBuilder().value(BLANK_LINE_STYLE, style);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the explicitly applied current blank line styling.
|
||||
* This doesn't return the implicit fallback used when not setting it explicitly.
|
||||
*
|
||||
* @return the blank line style
|
||||
* @since 4.3.0
|
||||
*/
|
||||
public @Nullable BlankLineStyle blankLineStyle() {
|
||||
return this.optionState().value(BLANK_LINE_STYLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether comment handling is enabled on this loader.
|
||||
*
|
||||
* <p>When comment handling is enabled, comments will be read from files
|
||||
* and written back to files where possible.</p>
|
||||
*
|
||||
* <p>The default value is {@code true}</p>
|
||||
*
|
||||
* @param enableComments whether comment handling should be enabled
|
||||
* @return this builder (for chaining)
|
||||
* @since 4.3.0
|
||||
*/
|
||||
public Builder commentsEnabled(final boolean enableComments) {
|
||||
this.optionStateBuilder().value(COMMENTS_ENABLED, enableComments);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether comment handling is enabled.
|
||||
*
|
||||
* @return whether comment handling is enabled
|
||||
* @see #commentsEnabled(boolean) for details on comment handling
|
||||
* @since 4.3.0
|
||||
*/
|
||||
public boolean commentsEnabled() {
|
||||
return this.optionState().value(COMMENTS_ENABLED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum length of a configuration line.
|
||||
*
|
||||
* <p>The default value is {@code 150}</p>
|
||||
*
|
||||
* @param lineLength the maximum length of a configuration line
|
||||
* @return this builder (for chaining)
|
||||
* @since 4.3.0
|
||||
*/
|
||||
public Builder lineLength(final int lineLength) {
|
||||
this.optionStateBuilder().value(LINE_LENGTH, lineLength);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum length of a configuration line.
|
||||
*
|
||||
* @return the maximum length of a configuration line
|
||||
* @see #lineLength(int) for details on the line length
|
||||
* @since 4.3.0
|
||||
*/
|
||||
public int lineLength() {
|
||||
return this.optionState().value(LINE_LENGTH);
|
||||
}
|
||||
|
||||
@Override
|
||||
public YamlConfigurationLoader build() {
|
||||
return new YamlConfigurationLoader(this);
|
||||
}
|
||||
}
|
||||
|
||||
private final LoaderOptions loaderOpts;
|
||||
private final DumperOptions dumperOpts;
|
||||
private final BlankLineStyle blankLineStyle;
|
||||
|
||||
private YamlConfigurationLoader(final Builder builder) {
|
||||
super(builder, new CommentHandler[] {CommentHandlers.HASH});
|
||||
this.loaderOpts = new LoaderOptions()
|
||||
// .setAcceptTabs(true) - requires Configurate's patched snakeyaml
|
||||
.setProcessComments(builder.commentsEnabled());
|
||||
this.loaderOpts.setCodePointLimit(Integer.MAX_VALUE);
|
||||
|
||||
this.dumperOpts = builder.options;
|
||||
this.dumperOpts.setDefaultFlowStyle(NodeStyle.asSnakeYaml(builder.optionState().value(Builder.NODE_STYLE)));
|
||||
this.dumperOpts.setIndent(builder.optionState().value(Builder.INDENT));
|
||||
this.dumperOpts.setProcessComments(builder.optionState().value(Builder.COMMENTS_ENABLED));
|
||||
this.dumperOpts.setWidth(builder.optionState().value(Builder.LINE_LENGTH));
|
||||
this.dumperOpts.setIndicatorIndent(builder.optionState().value(Builder.INDENT));
|
||||
this.dumperOpts.setIndentWithIndicator(true);
|
||||
|
||||
final @Nullable BlankLineStyle configuredBlankLineStyle = builder.optionState().value(Builder.BLANK_LINE_STYLE);
|
||||
this.blankLineStyle = configuredBlankLineStyle != null ? configuredBlankLineStyle : BlankLineStyle.NONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadInternal(final CommentedConfigurationNode node, final BufferedReader reader) {
|
||||
final Constructor constructor = new YamlConstructor(this.loaderOpts, node.options(), this.blankLineStyle);
|
||||
final Yaml yaml = new Yaml(constructor, new YamlRepresenter(this.blankLineStyle, true, this.dumperOpts), this.dumperOpts, this.loaderOpts);
|
||||
|
||||
@Nullable CommentedConfigurationNode loaded = yaml.load(reader);
|
||||
// when a file exists but is empty (or if the file only exists of comments), the first event will be StreamEnd.
|
||||
// getSingleNode will return null, getSingleData uses the Constructor of Tag Null, which just returns null.
|
||||
// So we have to map null to an empty root node.
|
||||
if (loaded == null) {
|
||||
loaded = CommentedConfigurationNode.root(node.options());
|
||||
}
|
||||
node.from(loaded);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void saveInternal(final ConfigurationNode node, final Writer writer) {
|
||||
final Constructor constructor = new YamlConstructor(this.loaderOpts, node.options(), this.blankLineStyle);
|
||||
final Yaml yaml = new Yaml(constructor, new YamlRepresenter(this.blankLineStyle, true, this.dumperOpts), this.dumperOpts, this.loaderOpts);
|
||||
yaml.dump(node, writer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommentedConfigurationNode createNode(final ConfigurationOptions options) {
|
||||
return CommentedConfigurationNode.root(options);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copied from GeyserMC's Configurate fork:
|
||||
* https://github.com/GeyserMC/Configurate/blob/f6aa72e6099f956e78b638a2a817429007c6898a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlConstructor.java
|
||||
*
|
||||
* Configurate
|
||||
* Copyright (C) zml and Configurate contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.papermc.paper.configuration.yaml;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.spongepowered.configurate.CommentedConfigurationNode;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.ConfigurationOptions;
|
||||
import org.spongepowered.configurate.loader.AbstractConfigurationLoader;
|
||||
import org.yaml.snakeyaml.LoaderOptions;
|
||||
import org.yaml.snakeyaml.comments.CommentLine;
|
||||
import org.yaml.snakeyaml.comments.CommentType;
|
||||
import org.yaml.snakeyaml.constructor.Constructor;
|
||||
import org.yaml.snakeyaml.nodes.MappingNode;
|
||||
import org.yaml.snakeyaml.nodes.Node;
|
||||
import org.yaml.snakeyaml.nodes.NodeId;
|
||||
import org.yaml.snakeyaml.nodes.NodeTuple;
|
||||
import org.yaml.snakeyaml.nodes.ScalarNode;
|
||||
import org.yaml.snakeyaml.nodes.SequenceNode;
|
||||
import org.yaml.snakeyaml.nodes.Tag;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
class YamlConstructor extends Constructor {
|
||||
|
||||
static final char BLANK_LINE_COMMENT_INDICATOR = '\0';
|
||||
private static final Pattern LINE_BREAK_PATTERN = Pattern.compile("\\R");
|
||||
|
||||
private final ConfigurationOptions options;
|
||||
private final BlankLineStyle blankLineStyle;
|
||||
|
||||
YamlConstructor(final LoaderOptions loadingConfig, final ConfigurationOptions options, final BlankLineStyle blankLineStyle) {
|
||||
super(loadingConfig);
|
||||
this.options = options;
|
||||
this.blankLineStyle = blankLineStyle;
|
||||
}
|
||||
|
||||
@Override
|
||||
@EnsuresNonNull("options")
|
||||
public Object getSingleData(final Class<?> type) {
|
||||
return super.getSingleData(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object constructObjectNoCheck(final Node yamlNode) {
|
||||
final CommentedConfigurationNode node = CommentedConfigurationNode.root(this.options);
|
||||
|
||||
// alright, let's first check some interesting behaviour.
|
||||
// When you have a file with only empty lines (has to be at least two),
|
||||
// a Tag of the type COMMENT will be executed.
|
||||
// It'll have a blockComment of the type empty line.
|
||||
// This is unlike the behaviour of an empty file and a file with only comments,
|
||||
// because both won't be seen as an object and will instead return null by Yaml#load.
|
||||
// Let's filter out the multi-empty-lines behaviour and return an empty node instead.
|
||||
if (yamlNode.getTag() == Tag.COMMENT) {
|
||||
return node;
|
||||
}
|
||||
|
||||
if (yamlNode.getNodeId() == NodeId.mapping) {
|
||||
// make sure to mark it as a map type, even if the map itself is empty
|
||||
node.raw(Collections.emptyMap());
|
||||
final MappingNode mapping = (MappingNode) yamlNode;
|
||||
if (mapping.getFlowStyle() != null) {
|
||||
node.hint(YamlConfigurationLoader.NODE_STYLE, NodeStyle.fromSnakeYaml(mapping.getFlowStyle()));
|
||||
}
|
||||
|
||||
for (final NodeTuple tuple : mapping.getValue()) {
|
||||
final ConfigurationNode keyNode = (ConfigurationNode) this.constructObject(tuple.getKeyNode());
|
||||
final Node valueNode = tuple.getValueNode();
|
||||
|
||||
// comments are on the key, not the value
|
||||
node.node(keyNode.raw())
|
||||
.from((ConfigurationNode) constructObject(valueNode))
|
||||
.comment(commentFor(tuple.getKeyNode().getBlockComments()));
|
||||
}
|
||||
|
||||
return node.comment(commentFor(yamlNode.getBlockComments()));
|
||||
}
|
||||
|
||||
final Object raw = super.constructObjectNoCheck(yamlNode);
|
||||
if (raw instanceof Collection<?>) {
|
||||
// make sure to mark it as a list type, even if the collection itself is empty
|
||||
node.raw(Collections.emptyList());
|
||||
if (((SequenceNode) yamlNode).getFlowStyle() != null) {
|
||||
node.hint(YamlConfigurationLoader.NODE_STYLE, NodeStyle.fromSnakeYaml(((SequenceNode) yamlNode).getFlowStyle()));
|
||||
}
|
||||
|
||||
for (final Object value : (Collection<?>) raw) {
|
||||
node.appendListNode().from((ConfigurationNode) value);
|
||||
}
|
||||
} else {
|
||||
if (yamlNode instanceof ScalarNode) {
|
||||
node.hint(YamlConfigurationLoader.SCALAR_STYLE, ScalarStyle.fromSnakeYaml(((ScalarNode) yamlNode).getScalarStyle()));
|
||||
}
|
||||
node.raw(raw);
|
||||
}
|
||||
|
||||
return node.comment(commentFor(yamlNode.getBlockComments()));
|
||||
}
|
||||
|
||||
private @Nullable String commentFor(final @Nullable List<CommentLine> commentLines) {
|
||||
if (commentLines == null || commentLines.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final StringBuilder outputBuilder = new StringBuilder();
|
||||
boolean first = true;
|
||||
|
||||
for (final CommentLine line : commentLines) {
|
||||
final boolean blank = line.getCommentType() == CommentType.BLANK_LINE;
|
||||
// Blank lines are considered formatting, and thus will not result in a comment.
|
||||
// Users can instead configure blank lines using the BLANK_LINE_STYLE_OVERRIDE.
|
||||
// Unless it's keep, as explained by its Javadoc.
|
||||
if (blank && this.blankLineStyle != BlankLineStyle.KEEP) {
|
||||
continue;
|
||||
}
|
||||
if (!first) {
|
||||
outputBuilder.append(AbstractConfigurationLoader.CONFIGURATE_LINE_SEPARATOR);
|
||||
}
|
||||
first = false;
|
||||
if (blank) {
|
||||
outputBuilder.append(BLANK_LINE_COMMENT_INDICATOR);
|
||||
continue;
|
||||
}
|
||||
|
||||
final String lineStripped = removeLineBreaksForLine(line.getValue());
|
||||
if (!lineStripped.isEmpty() && lineStripped.charAt(0) == ' ') {
|
||||
outputBuilder.append(lineStripped, 1, lineStripped.length());
|
||||
} else {
|
||||
outputBuilder.append(lineStripped);
|
||||
}
|
||||
}
|
||||
|
||||
return !first ? outputBuilder.toString() : null;
|
||||
}
|
||||
|
||||
private static String removeLineBreaksForLine(final String line) {
|
||||
return LINE_BREAK_PATTERN.matcher(line).replaceAll("");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
* Copied from GeyserMC's Configurate fork:
|
||||
* https://github.com/GeyserMC/Configurate/blob/f6aa72e6099f956e78b638a2a817429007c6898a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/YamlRepresenter.java
|
||||
*
|
||||
* Configurate
|
||||
* Copyright (C) zml and Configurate contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.papermc.paper.configuration.yaml;
|
||||
|
||||
import static org.spongepowered.configurate.loader.AbstractConfigurationLoader.CONFIGURATE_LINE_PATTERN;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.spongepowered.configurate.CommentedConfigurationNodeIntermediary;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.yaml.snakeyaml.DumperOptions;
|
||||
import org.yaml.snakeyaml.DumperOptions.FlowStyle;
|
||||
import org.yaml.snakeyaml.DumperOptions.ScalarStyle;
|
||||
import org.yaml.snakeyaml.comments.CommentLine;
|
||||
import org.yaml.snakeyaml.comments.CommentType;
|
||||
import org.yaml.snakeyaml.nodes.MappingNode;
|
||||
import org.yaml.snakeyaml.nodes.Node;
|
||||
import org.yaml.snakeyaml.nodes.NodeTuple;
|
||||
import org.yaml.snakeyaml.nodes.ScalarNode;
|
||||
import org.yaml.snakeyaml.nodes.SequenceNode;
|
||||
import org.yaml.snakeyaml.nodes.Tag;
|
||||
import org.yaml.snakeyaml.representer.Represent;
|
||||
import org.yaml.snakeyaml.representer.Representer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
final class YamlRepresenter extends Representer {
|
||||
|
||||
private static final CommentLine BLANK_LINE = new CommentLine(null, null, "", CommentType.BLANK_LINE);
|
||||
|
||||
private final BlankLineStyle blankLineStyle;
|
||||
private final boolean padComments;
|
||||
|
||||
YamlRepresenter(final BlankLineStyle blankLineStyle, final boolean padComments, final DumperOptions options) {
|
||||
super(options);
|
||||
this.blankLineStyle = blankLineStyle;
|
||||
this.padComments = padComments;
|
||||
multiRepresenters.put(ConfigurationNode.class, new ConfigurationNodeRepresent());
|
||||
nullRepresenter = new EmptyNullRepresenter();
|
||||
}
|
||||
|
||||
private final class ConfigurationNodeRepresent implements Represent {
|
||||
@Override
|
||||
public Node representData(final Object nodeObject) {
|
||||
final ConfigurationNode node = (ConfigurationNode) nodeObject;
|
||||
|
||||
final @Nullable Integer blankLineCount = node.ownHint(YamlConfigurationLoader.BLANK_LINE_STYLE_OVERRIDE);
|
||||
|
||||
final Node yamlNode;
|
||||
if (node.isMap()) {
|
||||
boolean first = true;
|
||||
boolean previousNested = false;
|
||||
|
||||
final List<NodeTuple> children = new ArrayList<>();
|
||||
for (Map.Entry<Object, ? extends ConfigurationNode> ent : node.childrenMap().entrySet()) {
|
||||
final ConfigurationNode child = ent.getValue();
|
||||
// SnakeYAML supports both key and value comments. Add the comments on the key
|
||||
final Node value = represent(child);
|
||||
final Node key = represent(ent.getKey());
|
||||
key.setBlockComments(value.getBlockComments());
|
||||
value.setBlockComments(Collections.emptyList());
|
||||
|
||||
final @Nullable Integer childBlankLineCount = child.ownHint(YamlConfigurationLoader.BLANK_LINE_STYLE_OVERRIDE);
|
||||
if (childBlankLineCount == null && !first && separateBefore(node, child, previousNested)) {
|
||||
addBlockCommentBlankLine(key, 1);
|
||||
}
|
||||
first = false;
|
||||
previousNested = isNested(child);
|
||||
|
||||
children.add(new NodeTuple(key, value));
|
||||
}
|
||||
yamlNode = new MappingNode(Tag.MAP, children, this.flowStyle(node));
|
||||
} else if (node.isList()) {
|
||||
final List<Node> children = new ArrayList<>();
|
||||
for (ConfigurationNode ent : node.childrenList()) {
|
||||
children.add(represent(ent));
|
||||
}
|
||||
yamlNode = new SequenceNode(Tag.SEQ, children, this.flowStyle(node));
|
||||
} else {
|
||||
final Node optionNode = represent(node.rawScalar());
|
||||
final io.papermc.paper.configuration.yaml.@Nullable ScalarStyle requestedStyle
|
||||
= node.ownHint(YamlConfigurationLoader.SCALAR_STYLE);
|
||||
if (optionNode instanceof ScalarNode && requestedStyle != null) {
|
||||
final ScalarNode scalar = (ScalarNode) optionNode;
|
||||
yamlNode = new ScalarNode(
|
||||
scalar.getTag(),
|
||||
scalar.getValue(),
|
||||
scalar.getStartMark(),
|
||||
scalar.getEndMark(),
|
||||
io.papermc.paper.configuration.yaml.ScalarStyle.asSnakeYaml(requestedStyle, scalar.getScalarStyle())
|
||||
);
|
||||
} else {
|
||||
yamlNode = optionNode;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (node instanceof CommentedConfigurationNodeIntermediary<?>) {
|
||||
final @Nullable String nodeComment = ((CommentedConfigurationNodeIntermediary<?>) node).comment();
|
||||
if (nodeComment != null) {
|
||||
yamlNode.setBlockComments(
|
||||
Arrays.stream(CONFIGURATE_LINE_PATTERN.split(nodeComment, -1))
|
||||
.map(this::commentLineFor)
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (blankLineCount != null && blankLineCount > 0) {
|
||||
addBlockCommentBlankLine(yamlNode, blankLineCount);
|
||||
}
|
||||
|
||||
return yamlNode;
|
||||
}
|
||||
|
||||
private FlowStyle flowStyle(final ConfigurationNode node) {
|
||||
final @Nullable NodeStyle requested = node.ownHint(YamlConfigurationLoader.NODE_STYLE);
|
||||
return NodeStyle.asSnakeYaml(requested);
|
||||
}
|
||||
|
||||
private boolean separateBefore(final ConfigurationNode parent, final ConfigurationNode child, final boolean previousNested) {
|
||||
switch (YamlRepresenter.this.blankLineStyle) {
|
||||
case ROOT_CHILDREN:
|
||||
return parent.parent() == null;
|
||||
case AFTER_NESTED:
|
||||
return previousNested;
|
||||
case BEFORE_COMMENT:
|
||||
return child instanceof CommentedConfigurationNodeIntermediary<?>
|
||||
&& ((CommentedConfigurationNodeIntermediary<?>) child).comment() != null
|
||||
&& !(parent.parent() != null && parent.parent().isList());
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isNested(final ConfigurationNode node) {
|
||||
return (node.isMap() || node.isList()) && !node.empty();
|
||||
}
|
||||
|
||||
private void addBlockCommentBlankLine(final Node node, final int count) {
|
||||
@Nullable List<CommentLine> comments = node.getBlockComments();
|
||||
|
||||
if (comments == null) {
|
||||
comments = new ArrayList<>();
|
||||
} else {
|
||||
comments = new ArrayList<>(comments);
|
||||
}
|
||||
|
||||
node.setBlockComments(comments);
|
||||
comments.addAll(0, Collections.nCopies(count, BLANK_LINE));
|
||||
}
|
||||
|
||||
private CommentLine commentLineFor(final String comment) {
|
||||
if (comment.length() == 1 && comment.charAt(0) == YamlConstructor.BLANK_LINE_COMMENT_INDICATOR) {
|
||||
return BLANK_LINE;
|
||||
}
|
||||
|
||||
if (!YamlRepresenter.this.padComments || comment.isEmpty() || comment.charAt(0) == '#') {
|
||||
return new CommentLine(null, null, comment, CommentType.BLOCK);
|
||||
}
|
||||
|
||||
// prepend a space before the comment:
|
||||
// before: #hello
|
||||
// after: # hello
|
||||
return new CommentLine(null, null, " " + comment, CommentType.BLOCK);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class EmptyNullRepresenter implements Represent {
|
||||
@Override
|
||||
public Node representData(final Object data) {
|
||||
return new ScalarNode(Tag.NULL, "", null, null, ScalarStyle.PLAIN);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copied from GeyserMC's Configurate fork:
|
||||
* https://github.com/GeyserMC/Configurate/blob/f6aa72e6099f956e78b638a2a817429007c6898a/format/yaml/src/main/java/org/spongepowered/configurate/yaml/package-info.java
|
||||
*
|
||||
* Configurate
|
||||
* Copyright (C) zml and Configurate contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* Configuration loader for the <a href="https://yaml.org/spec/1.1/">YAML 1.1</a> format.
|
||||
*
|
||||
* <p>While by default this loader only declares <a href="https://yaml.org/type/index.html">the YAML 1.1 global tags</a> as
|
||||
* supported types, the underlying library is capable of serializing any POJO,
|
||||
* and the {@link org.spongepowered.configurate.ConfigurationOptions}'s native
|
||||
* types field can be adjusted to customize output.
|
||||
*/
|
||||
@DefaultQualifier(NonNull.class)
|
||||
package io.papermc.paper.configuration.yaml;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.framework.qual.DefaultQualifier;
|
||||
@@ -1,7 +1,7 @@
|
||||
package io.papermc.paper.plugin;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import io.papermc.paper.configuration.PaperConfigurations;
|
||||
import io.papermc.paper.configuration.PaperConfigurationsInitializer;
|
||||
import io.papermc.paper.plugin.entrypoint.Entrypoint;
|
||||
import io.papermc.paper.plugin.entrypoint.LaunchEntryPointHandler;
|
||||
import io.papermc.paper.plugin.provider.PluginProvider;
|
||||
@@ -11,7 +11,6 @@ import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import joptsimple.OptionSet;
|
||||
import net.minecraft.server.dedicated.DedicatedServer;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
@@ -34,14 +33,11 @@ public class PluginInitializerManager {
|
||||
}
|
||||
|
||||
private static PluginInitializerManager parse(@NotNull final OptionSet minecraftOptionSet) throws Exception {
|
||||
// We have to load the bukkit configuration inorder to get the update folder location.
|
||||
final File configFileLocationBukkit = (File) minecraftOptionSet.valueOf("bukkit-settings");
|
||||
|
||||
final Path pluginDirectory = ((File) minecraftOptionSet.valueOf("plugins")).toPath();
|
||||
|
||||
final YamlConfiguration configuration = PaperConfigurations.loadLegacyConfigFile(configFileLocationBukkit);
|
||||
|
||||
final String updateDirectoryName = configuration.getString("settings.update-folder", "update");
|
||||
// This runs before the registries are bootstrapped, so the full global config cannot be read here.
|
||||
// The update folder comes from the early initialization pass instead.
|
||||
final String updateDirectoryName = PaperConfigurationsInitializer.earlyGlobalConfig().misc.updateFolder;
|
||||
if (updateDirectoryName.isBlank()) {
|
||||
return new PluginInitializerManager(pluginDirectory, null);
|
||||
}
|
||||
@@ -50,7 +46,7 @@ public class PluginInitializerManager {
|
||||
if (!Files.isDirectory(resolvedUpdateDirectory)) {
|
||||
if (Files.exists(resolvedUpdateDirectory)) {
|
||||
LOGGER.error("Misconfigured update directory!");
|
||||
LOGGER.error("Your configured update directory ({}) in bukkit.yml is pointing to a non-directory path. " +
|
||||
LOGGER.error("Your configured update directory ({}) in initialization.update-folder is pointing to a non-directory path. " +
|
||||
"Auto updating functionality will not work.", resolvedUpdateDirectory);
|
||||
}
|
||||
return new PluginInitializerManager(pluginDirectory, null);
|
||||
@@ -67,7 +63,7 @@ public class PluginInitializerManager {
|
||||
|
||||
if (isSameFile) {
|
||||
LOGGER.error("Misconfigured update directory!");
|
||||
LOGGER.error(("Your configured update directory (%s) in bukkit.yml is pointing to the same location as the plugin directory (%s). " +
|
||||
LOGGER.error(("Your configured update directory (%s) in initialization.update-folder is pointing to the same location as the plugin directory (%s). " +
|
||||
"Disabling auto updating functionality.").formatted(resolvedUpdateDirectory, pluginDirectory));
|
||||
|
||||
return new PluginInitializerManager(pluginDirectory, null);
|
||||
@@ -97,7 +93,6 @@ public class PluginInitializerManager {
|
||||
|
||||
public static void load(OptionSet optionSet) throws Exception {
|
||||
LOGGER.info("Initializing plugins...");
|
||||
// We have to load the bukkit configuration inorder to get the update folder location.
|
||||
io.papermc.paper.plugin.PluginInitializerManager pluginSystem = io.papermc.paper.plugin.PluginInitializerManager.init(optionSet);
|
||||
|
||||
// Register the default plugin directory
|
||||
|
||||
+2
-2
@@ -5,6 +5,8 @@ import com.google.common.collect.ImmutableList;
|
||||
import io.papermc.paper.configuration.constraint.Constraint;
|
||||
import io.papermc.paper.configuration.serializer.ComponentSerializer;
|
||||
import io.papermc.paper.configuration.serializer.EnumValueSerializer;
|
||||
import io.papermc.paper.configuration.yaml.NodeStyle;
|
||||
import io.papermc.paper.configuration.yaml.YamlConfigurationLoader;
|
||||
import io.papermc.paper.plugin.configuration.PluginMeta;
|
||||
import io.papermc.paper.plugin.provider.configuration.serializer.PermissionConfigurationSerializer;
|
||||
import io.papermc.paper.plugin.provider.configuration.serializer.constraints.PluginConfigConstraints;
|
||||
@@ -28,8 +30,6 @@ import org.spongepowered.configurate.objectmapping.ObjectMapper;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Required;
|
||||
import org.spongepowered.configurate.serialize.ScalarSerializer;
|
||||
import org.spongepowered.configurate.serialize.SerializationException;
|
||||
import org.spongepowered.configurate.yaml.NodeStyle;
|
||||
import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.util.EnumMap;
|
||||
|
||||
@@ -103,7 +103,7 @@ public final class PaperFilledProfileCache {
|
||||
}
|
||||
|
||||
private int maxSize() {
|
||||
return org.spigotmc.SpigotConfig.userCacheCap;
|
||||
return io.papermc.paper.configuration.GlobalConfiguration.get().players.userCacheSize;
|
||||
}
|
||||
|
||||
private boolean tryCancelCleanup() {
|
||||
|
||||
@@ -191,7 +191,7 @@ public class CraftChunk implements Chunk {
|
||||
@Override
|
||||
public boolean isSlimeChunk() {
|
||||
// 987234911L is taken from Slime when seeing if a slime can spawn in a chunk
|
||||
return this.level.paperConfig().entities.spawning.allChunksAreSlimeChunks || WorldgenRandom.seedSlimeChunk(this.getX(), this.getZ(), this.getWorld().getSeed(), level.spigotConfig.slimeSeed).nextInt(10) == 0; // Paper
|
||||
return this.level.paperConfig().entities.spawning.allChunksAreSlimeChunks || WorldgenRandom.seedSlimeChunk(this.getX(), this.getZ(), this.getWorld().getSeed(), level.paperConfig().seeds.slime).nextInt(10) == 0; // Paper
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -10,7 +10,9 @@ import com.google.common.collect.MapMaker;
|
||||
import com.mojang.brigadier.StringReader;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.mojang.serialization.JsonOps;
|
||||
import io.papermc.paper.adventure.PaperAdventure;
|
||||
import io.papermc.paper.configuration.GlobalConfiguration;
|
||||
import io.papermc.paper.configuration.PaperConfigurations;
|
||||
import io.papermc.paper.configuration.PaperServerConfiguration;
|
||||
import io.papermc.paper.configuration.ServerConfiguration;
|
||||
import io.papermc.paper.util.MCUtil;
|
||||
@@ -45,6 +47,7 @@ import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.imageio.ImageIO;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.minecraft.Optionull;
|
||||
import net.minecraft.advancements.AdvancementHolder;
|
||||
@@ -80,6 +83,7 @@ import net.minecraft.util.Util;
|
||||
import net.minecraft.util.datafix.DataFixers;
|
||||
import net.minecraft.world.damagesource.DamageType;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.entity.MobCategory;
|
||||
import net.minecraft.world.entity.ai.village.VillageSiege;
|
||||
import net.minecraft.world.entity.npc.CatSpawner;
|
||||
import net.minecraft.world.entity.npc.wanderingtrader.WanderingTraderSpawner;
|
||||
@@ -258,14 +262,12 @@ public final class CraftServer implements Server {
|
||||
final DedicatedServer console;
|
||||
private final DedicatedPlayerList playerList;
|
||||
private final Map<String, World> worlds = new LinkedHashMap<>();
|
||||
private YamlConfiguration configuration;
|
||||
private YamlConfiguration commandsConfiguration;
|
||||
private final Yaml yaml = new Yaml(new SafeConstructor(new LoaderOptions()));
|
||||
private final Map<UUID, OfflinePlayer> offlinePlayers = new MapMaker().weakValues().makeMap();
|
||||
private final EntityMetadataStore entityMetadata = new EntityMetadataStore();
|
||||
private final PlayerMetadataStore playerMetadata = new PlayerMetadataStore();
|
||||
private final WorldMetadataStore worldMetadata = new WorldMetadataStore();
|
||||
private final Object2IntOpenHashMap<SpawnCategory> spawnCategoryLimit = new Object2IntOpenHashMap<>();
|
||||
private File container;
|
||||
private WarningState warningState = WarningState.DEFAULT;
|
||||
public ApiVersion minimumAPI;
|
||||
@@ -399,20 +401,6 @@ public final class CraftServer implements Server {
|
||||
this.getLogger().info("Console input is disabled due to --noconsole command argument");
|
||||
}
|
||||
|
||||
this.configuration = YamlConfiguration.loadConfiguration(this.getConfigFile());
|
||||
this.configuration.options().copyDefaults(true);
|
||||
YamlConfiguration configurationDefaults = YamlConfiguration.loadConfiguration(new InputStreamReader(this.getClass().getClassLoader().getResourceAsStream("configurations/bukkit.yml"), StandardCharsets.UTF_8));
|
||||
this.configuration.setDefaults(configurationDefaults);
|
||||
this.configuration.options().setHeader(configurationDefaults.options().getHeader());
|
||||
ConfigurationSection legacyAlias = null;
|
||||
if (!this.configuration.isString("aliases")) {
|
||||
legacyAlias = this.configuration.getConfigurationSection("aliases");
|
||||
this.configuration.set("aliases", "now-in-commands.yml");
|
||||
}
|
||||
this.saveConfig();
|
||||
if (this.getCommandsConfigFile().isFile()) {
|
||||
legacyAlias = null;
|
||||
}
|
||||
this.commandsConfiguration = YamlConfiguration.loadConfiguration(this.getCommandsConfigFile());
|
||||
this.commandsConfiguration.options().copyDefaults(true);
|
||||
// Paper start - don't enforce icanhasbukkit default if alias block exists
|
||||
@@ -421,40 +409,16 @@ public final class CraftServer implements Server {
|
||||
this.commandsConfiguration.setDefaults(commandsDefaults);
|
||||
// Paper end - don't enforce icanhasbukkit default if alias block exists
|
||||
this.commandsConfiguration.options().setHeader(commandsDefaults.options().getHeader());
|
||||
this.saveCommandsConfig();
|
||||
|
||||
// Migrate aliases from old file and add previously implicit $1- to pass all arguments
|
||||
if (legacyAlias != null) {
|
||||
ConfigurationSection aliases = this.commandsConfiguration.createSection("aliases");
|
||||
for (String key : legacyAlias.getKeys(false)) {
|
||||
List<String> commands = new ArrayList<>();
|
||||
|
||||
if (legacyAlias.isList(key)) {
|
||||
for (String command : legacyAlias.getStringList(key)) {
|
||||
commands.add(command + " $1-");
|
||||
}
|
||||
} else {
|
||||
commands.add(legacyAlias.getString(key) + " $1-");
|
||||
}
|
||||
|
||||
aliases.set(key, commands);
|
||||
}
|
||||
}
|
||||
|
||||
this.saveCommandsConfig();
|
||||
this.overrideAllCommandBlockCommands = this.commandsConfiguration.getStringList("command-block-overrides").contains("*");
|
||||
this.ignoreVanillaPermissions = this.commandsConfiguration.getBoolean("ignore-vanilla-permissions");
|
||||
this.overrideSpawnLimits();
|
||||
console.autosavePeriod = this.configuration.getInt("ticks-per.autosave");
|
||||
this.warningState = WarningState.value(this.configuration.getString("settings.deprecated-verbose"));
|
||||
TicketType.PLUGIN_TYPE_TIMEOUT = this.configuration.getInt("chunk-gc.period-in-ticks");
|
||||
this.minimumAPI = ApiVersion.getOrCreateVersion(this.configuration.getString("settings.minimum-api"));
|
||||
|
||||
this.applyPaperConfiguration(); // Paper - moved from bukkit.yml
|
||||
this.loadIcon();
|
||||
this.loadCompatibilities();
|
||||
CraftMagicNumbers.INSTANCE.getCommodore().updateReroute(activeCompatibilities::contains);
|
||||
|
||||
// Set map color cache
|
||||
if (this.configuration.getBoolean("settings.use-map-color-cache")) {
|
||||
if (GlobalConfiguration.get().misc.useMapColorCache) {
|
||||
MapPalette.setMapColorCache(new CraftMapColorCache(this.logger));
|
||||
}
|
||||
this.potionBrewer = new io.papermc.paper.potion.PaperPotionBrewer(console); // Paper - custom potion mixes
|
||||
@@ -462,32 +426,35 @@ public final class CraftServer implements Server {
|
||||
this.spark = new io.papermc.paper.SparksFly(this); // Paper - spark
|
||||
}
|
||||
|
||||
public boolean getCommandBlockOverride(String command) {
|
||||
return this.overrideAllCommandBlockCommands || this.commandsConfiguration.getStringList("command-block-overrides").contains(command);
|
||||
}
|
||||
|
||||
private File getConfigFile() {
|
||||
return (File) this.console.options.valueOf("bukkit-settings");
|
||||
}
|
||||
|
||||
private File getCommandsConfigFile() {
|
||||
return (File) this.console.options.valueOf("commands-settings");
|
||||
}
|
||||
|
||||
private void overrideSpawnLimits() {
|
||||
for (SpawnCategory spawnCategory : SpawnCategory.values()) {
|
||||
if (CraftSpawnCategory.isValidForLimits(spawnCategory)) {
|
||||
this.spawnCategoryLimit.put(spawnCategory, this.configuration.getInt(CraftSpawnCategory.getConfigNameSpawnLimit(spawnCategory)));
|
||||
public void applyPaperConfiguration() {
|
||||
final GlobalConfiguration config = GlobalConfiguration.get();
|
||||
this.console.autosavePeriod = config.misc.autoSaveInterval;
|
||||
this.warningState = config.misc.deprecatedVerbose;
|
||||
TicketType.PLUGIN_TYPE_TIMEOUT = config.chunkSystem.pluginTicketTimeout;
|
||||
this.minimumAPI = ApiVersion.getOrCreateVersion(config.misc.minimumApi);
|
||||
for (final ServerLevel world : this.console.getAllLevels()) {
|
||||
for (final SpawnCategory spawnCategory : SpawnCategory.values()) {
|
||||
if (CraftSpawnCategory.isValidForLimits(spawnCategory)) {
|
||||
final MobCategory category = CraftSpawnCategory.toNMS(spawnCategory);
|
||||
world.ticksPerSpawnCategory.put(spawnCategory, world.paperConfig().entities.spawning.ticksPerSpawn.getInt(category));
|
||||
world.getWorld().setSpawnLimit(spawnCategory, world.paperConfig().entities.spawning.spawnLimits.getInt(category));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void saveConfig() {
|
||||
try {
|
||||
this.configuration.save(this.getConfigFile());
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(CraftServer.class.getName()).log(Level.SEVERE, "Could not save " + this.getConfigFile(), ex);
|
||||
}
|
||||
public void reloadPaperConfiguration() {
|
||||
this.console.paperConfigurations.reloadConfigs(this.console);
|
||||
this.applyPaperConfiguration();
|
||||
this.syncCommands();
|
||||
}
|
||||
|
||||
public boolean getCommandBlockOverride(String command) {
|
||||
return this.overrideAllCommandBlockCommands || this.commandsConfiguration.getStringList("command-block-overrides").contains(command);
|
||||
}
|
||||
|
||||
private File getCommandsConfigFile() {
|
||||
return (File) this.console.options.valueOf("commands-settings");
|
||||
}
|
||||
|
||||
private void saveCommandsConfig() {
|
||||
@@ -498,33 +465,11 @@ public final class CraftServer implements Server {
|
||||
}
|
||||
}
|
||||
|
||||
private void loadCompatibilities() {
|
||||
if (true) return; // Paper - Big nope
|
||||
ConfigurationSection compatibilities = this.configuration.getConfigurationSection("settings.compatibility");
|
||||
if (compatibilities == null) {
|
||||
this.activeCompatibilities = Collections.emptySet();
|
||||
return;
|
||||
}
|
||||
|
||||
this.activeCompatibilities = compatibilities
|
||||
.getKeys(false)
|
||||
.stream()
|
||||
.filter(compatibilities::getBoolean)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
if (!this.activeCompatibilities.isEmpty()) {
|
||||
this.logger.info("Using following compatibilities: `" + Joiner.on("`, `").join(this.activeCompatibilities) + "`, this will affect performance and other plugins behavior.");
|
||||
this.logger.info("Only use when necessary and prefer updating plugins if possible.");
|
||||
}
|
||||
|
||||
if (this.activeCompatibilities.contains("enum-compatibility-mode")) {
|
||||
this.getLogger().warning("Loading plugins in enum compatibility mode. This will affect plugin performance. Use only as a transition period or when absolutely necessary.");
|
||||
} else if (System.getProperty("RemoveEnumBanner") == null) {
|
||||
// TODO 2024-06-16: Remove in newer version
|
||||
this.getLogger().info("*** This version of Spigot contains changes to some enums. If you notice that plugins no longer work after updating, please report this to the developers of those plugins first. ***");
|
||||
this.getLogger().info("*** If you cannot update those plugins, you can try setting `settings.compatibility.enum-compatibility-mode` to `true` in `bukkit.yml`. ***");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void loadPlugins() {
|
||||
io.papermc.paper.plugin.entrypoint.LaunchEntryPointHandler.INSTANCE.enter(io.papermc.paper.plugin.entrypoint.Entrypoint.PLUGIN); // Paper - replace implementation
|
||||
@@ -561,7 +506,7 @@ public final class CraftServer implements Server {
|
||||
if (type == PluginLoadOrder.STARTUP) {
|
||||
this.helpMap.clear();
|
||||
this.helpMap.initializeGeneralTopics();
|
||||
if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.loadPermissionsYmlBeforePlugins) loadCustomPermissions(); // Paper
|
||||
if (GlobalConfiguration.get().misc.loadPermissionsYmlBeforePlugins) loadCustomPermissions(); // Paper
|
||||
}
|
||||
|
||||
Plugin[] plugins = this.pluginManager.getPlugins();
|
||||
@@ -578,7 +523,7 @@ public final class CraftServer implements Server {
|
||||
// Spigot end
|
||||
DefaultPermissions.registerCorePermissions();
|
||||
CraftDefaultPermissions.registerCorePermissions();
|
||||
if (!io.papermc.paper.configuration.GlobalConfiguration.get().misc.loadPermissionsYmlBeforePlugins) this.loadCustomPermissions(); // Paper
|
||||
if (!GlobalConfiguration.get().misc.loadPermissionsYmlBeforePlugins) this.loadCustomPermissions(); // Paper
|
||||
this.syncCommands();
|
||||
}
|
||||
}
|
||||
@@ -764,7 +709,7 @@ public final class CraftServer implements Server {
|
||||
|
||||
@Override
|
||||
public boolean getAllowEnd() {
|
||||
return this.configuration.getBoolean("settings.allow-end");
|
||||
return GlobalConfiguration.get().misc.enableEnd;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -778,11 +723,11 @@ public final class CraftServer implements Server {
|
||||
}
|
||||
|
||||
public boolean getWarnOnOverload() {
|
||||
return this.configuration.getBoolean("settings.warn-on-overload");
|
||||
return true; // Paper - option removed, nothing internal read it
|
||||
}
|
||||
|
||||
public boolean getQueryPlugins() {
|
||||
return this.configuration.getBoolean("settings.query-plugins");
|
||||
return GlobalConfiguration.get().misc.queryPlugins;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -838,21 +783,21 @@ public final class CraftServer implements Server {
|
||||
|
||||
@Override
|
||||
public String getUpdateFolder() {
|
||||
return this.configuration.getString("settings.update-folder", "update");
|
||||
return GlobalConfiguration.get().misc.updateFolder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getUpdateFolderFile() {
|
||||
return new File((File) this.console.options.valueOf("plugins"), this.configuration.getString("settings.update-folder", "update"));
|
||||
return new File((File) this.console.options.valueOf("plugins"), GlobalConfiguration.get().misc.updateFolder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getConnectionThrottle() {
|
||||
// Spigot start - Automatically set connection throttle for bungee configurations
|
||||
if (org.spigotmc.SpigotConfig.bungee || io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) { // Paper - Add Velocity IP Forwarding Support
|
||||
if (GlobalConfiguration.get().proxies.bungeeCord.enabled || GlobalConfiguration.get().proxies.velocity.enabled) { // Paper - Add Velocity IP Forwarding Support
|
||||
return -1;
|
||||
} else {
|
||||
return this.configuration.getInt("settings.connection-throttle");
|
||||
return GlobalConfiguration.get().misc.connectionThrottle;
|
||||
}
|
||||
// Spigot end
|
||||
}
|
||||
@@ -861,7 +806,11 @@ public final class CraftServer implements Server {
|
||||
public int getTicksPerSpawns(SpawnCategory spawnCategory) {
|
||||
Preconditions.checkArgument(spawnCategory != null, "SpawnCategory cannot be null");
|
||||
Preconditions.checkArgument(CraftSpawnCategory.isValidForLimits(spawnCategory), "SpawnCategory.%s are not supported", spawnCategory);
|
||||
return this.configuration.getInt(CraftSpawnCategory.getConfigNameTicksPerSpawn(spawnCategory));
|
||||
// Paper - spawn intervals are per world; report the primary world's
|
||||
final List<World> worlds = this.getWorlds();
|
||||
return worlds.isEmpty()
|
||||
? (int) CraftSpawnCategory.getDefaultTicksPerSpawn(spawnCategory)
|
||||
: (int) worlds.get(0).getTicksPerSpawns(spawnCategory);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -936,21 +885,14 @@ public final class CraftServer implements Server {
|
||||
// Paper end - lifecycle events
|
||||
org.spigotmc.WatchdogThread.hasStarted = false; // Paper - Disable watchdog early timeout on reload
|
||||
this.reloadCount++;
|
||||
this.configuration = YamlConfiguration.loadConfiguration(this.getConfigFile());
|
||||
this.commandsConfiguration = YamlConfiguration.loadConfiguration(this.getCommandsConfigFile());
|
||||
|
||||
this.console.settings = new DedicatedServerSettings(this.console.options);
|
||||
DedicatedServerProperties config = this.console.settings.getProperties();
|
||||
|
||||
this.console.setMotd(config.motd.get());
|
||||
this.overrideSpawnLimits();
|
||||
this.warningState = WarningState.value(this.configuration.getString("settings.deprecated-verbose"));
|
||||
TicketType.PLUGIN_TYPE_TIMEOUT = this.configuration.getInt("chunk-gc.period-in-ticks");
|
||||
this.minimumAPI = ApiVersion.getOrCreateVersion(this.configuration.getString("settings.minimum-api"));
|
||||
this.printSaveWarning = false;
|
||||
this.console.autosavePeriod = this.configuration.getInt("ticks-per.autosave");
|
||||
this.loadIcon();
|
||||
this.loadCompatibilities();
|
||||
CraftMagicNumbers.INSTANCE.getCommodore().updateReroute(activeCompatibilities::contains);
|
||||
|
||||
try {
|
||||
@@ -964,23 +906,11 @@ public final class CraftServer implements Server {
|
||||
this.logger.log(Level.WARNING, "Failed to load banned-players.json, " + ex.getMessage());
|
||||
}
|
||||
|
||||
org.spigotmc.SpigotConfig.init((File) this.console.options.valueOf("spigot-settings")); // Spigot
|
||||
this.console.paperConfigurations.reloadConfigs(this.console);
|
||||
this.applyPaperConfiguration();
|
||||
for (ServerLevel world : this.console.getAllLevels()) {
|
||||
// world.serverLevelData.setDifficulty(config.difficulty); // Paper - per level difficulty
|
||||
world.setSpawnSettings(world.isSpawningMonsters()); // Paper - per level difficulty (from MinecraftServer#setDifficulty(ServerLevel, Difficulty, boolean))
|
||||
|
||||
for (SpawnCategory spawnCategory : SpawnCategory.values()) {
|
||||
if (CraftSpawnCategory.isValidForLimits(spawnCategory)) {
|
||||
long ticksPerCategorySpawn = this.getTicksPerSpawns(spawnCategory);
|
||||
if (ticksPerCategorySpawn < 0) {
|
||||
world.ticksPerSpawnCategory.put(spawnCategory, CraftSpawnCategory.getDefaultTicksPerSpawn(spawnCategory));
|
||||
} else {
|
||||
world.ticksPerSpawnCategory.put(spawnCategory, ticksPerCategorySpawn);
|
||||
}
|
||||
}
|
||||
}
|
||||
world.spigotConfig.init(); // Spigot
|
||||
}
|
||||
|
||||
Plugin[] pluginClone = pluginManager.getPlugins().clone(); // Paper
|
||||
@@ -995,7 +925,6 @@ public final class CraftServer implements Server {
|
||||
}
|
||||
// Paper end
|
||||
this.reloadData();
|
||||
org.spigotmc.SpigotConfig.registerCommands(); // Spigot
|
||||
io.papermc.paper.command.PaperCommands.registerCommands(this.console); // Paper
|
||||
this.spark.registerCommandBeforePlugins(this); // Paper - spark
|
||||
this.overrideAllCommandBlockCommands = this.commandsConfiguration.getStringList("command-block-overrides").contains("*");
|
||||
@@ -1093,7 +1022,7 @@ public final class CraftServer implements Server {
|
||||
}
|
||||
|
||||
private void loadCustomPermissions() {
|
||||
File file = new File(this.configuration.getString("settings.permissions-file"));
|
||||
File file = new File(GlobalConfiguration.get().misc.permissionsFile);
|
||||
if (!file.isFile()) {
|
||||
return;
|
||||
}
|
||||
@@ -1683,7 +1612,7 @@ public final class CraftServer implements Server {
|
||||
@Override
|
||||
@Deprecated // Paper
|
||||
public String getShutdownMessage() {
|
||||
return this.configuration.getString("settings.shutdown-message");
|
||||
return CraftChatMessage.fromComponent(PaperAdventure.asVanilla(GlobalConfiguration.get().messages.shutdown));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1732,14 +1661,12 @@ public final class CraftServer implements Server {
|
||||
}
|
||||
|
||||
public ChunkGenerator getGenerator(String world) {
|
||||
ConfigurationSection section = this.configuration.getConfigurationSection("worlds");
|
||||
final GlobalConfiguration.WorldGenerators generators = GlobalConfiguration.get().worldGenerators.get(world); // Paper - moved from bukkit.yml
|
||||
ChunkGenerator result = null;
|
||||
|
||||
if (section != null) {
|
||||
section = section.getConfigurationSection(world);
|
||||
|
||||
if (section != null) {
|
||||
String name = section.getString("generator");
|
||||
if (generators != null) {
|
||||
{
|
||||
String name = generators.generator();
|
||||
|
||||
if (name != null && !name.isEmpty()) {
|
||||
String[] split = name.split(":", 2);
|
||||
@@ -1768,14 +1695,12 @@ public final class CraftServer implements Server {
|
||||
}
|
||||
|
||||
public BiomeProvider getBiomeProvider(String world) {
|
||||
ConfigurationSection section = this.configuration.getConfigurationSection("worlds");
|
||||
GlobalConfiguration.WorldGenerators generators = GlobalConfiguration.get().worldGenerators.get(world); // Paper - moved from bukkit.yml
|
||||
BiomeProvider result = null;
|
||||
|
||||
if (section != null) {
|
||||
section = section.getConfigurationSection(world);
|
||||
|
||||
if (section != null) {
|
||||
String name = section.getString("biome-provider");
|
||||
if (generators != null) {
|
||||
{
|
||||
String name = generators.biomeProvider();
|
||||
|
||||
if (name != null && !name.isEmpty()) {
|
||||
String[] split = name.split(":", 2);
|
||||
@@ -1909,7 +1834,7 @@ public final class CraftServer implements Server {
|
||||
}
|
||||
NameAndId nameAndId;
|
||||
// Only fetch an online UUID in online mode
|
||||
if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode()) {
|
||||
if (GlobalConfiguration.get().proxies.isProxyOnlineMode()) {
|
||||
nameAndId = console.services().nameToIdCache().get(name).orElse(null);
|
||||
} else {
|
||||
// Make an OfflinePlayer using an offline mode UUID since the name has no profile
|
||||
@@ -1927,7 +1852,7 @@ public final class CraftServer implements Server {
|
||||
if (result == null) {
|
||||
NameAndId profile = null;
|
||||
// Only fetch an online UUID in online mode
|
||||
if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode()) { // Paper - Add setting for proxy online mode status
|
||||
if (GlobalConfiguration.get().proxies.isProxyOnlineMode()) { // Paper - Add setting for proxy online mode status
|
||||
// This is potentially blocking :(
|
||||
profile = this.console.services().nameToIdCache().get(name).orElse(null);
|
||||
}
|
||||
@@ -2288,11 +2213,8 @@ public final class CraftServer implements Server {
|
||||
public int getSpawnLimit(SpawnCategory spawnCategory) {
|
||||
Preconditions.checkArgument(spawnCategory != null, "SpawnCategory cannot be null");
|
||||
Preconditions.checkArgument(CraftSpawnCategory.isValidForLimits(spawnCategory), "SpawnCategory." + spawnCategory + " does not have a spawn limit.");
|
||||
return this.getSpawnLimitUnsafe(spawnCategory);
|
||||
}
|
||||
|
||||
public int getSpawnLimitUnsafe(final SpawnCategory spawnCategory) {
|
||||
return this.spawnCategoryLimit.getOrDefault(spawnCategory, -1);
|
||||
final List<World> worlds = this.getWorlds();
|
||||
return worlds.isEmpty() ? -1 : worlds.get(0).getSpawnLimit(spawnCategory);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -2695,27 +2617,6 @@ public final class CraftServer implements Server {
|
||||
|
||||
private final org.bukkit.Server.Spigot spigot = new org.bukkit.Server.Spigot() {
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public YamlConfiguration getConfig() {
|
||||
return org.spigotmc.SpigotConfig.config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public YamlConfiguration getBukkitConfig() {
|
||||
return configuration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public YamlConfiguration getSpigotConfig() {
|
||||
return org.spigotmc.SpigotConfig.config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public YamlConfiguration getPaperConfig() {
|
||||
return CraftServer.this.console.paperConfigurations.createLegacyObject(CraftServer.this.console);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restart() {
|
||||
CraftServer.this.restart();
|
||||
@@ -2832,7 +2733,7 @@ public final class CraftServer implements Server {
|
||||
@Override
|
||||
public void reloadPermissions() {
|
||||
pluginManager.clearPermissions();
|
||||
if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.loadPermissionsYmlBeforePlugins) loadCustomPermissions();
|
||||
if (GlobalConfiguration.get().misc.loadPermissionsYmlBeforePlugins) loadCustomPermissions();
|
||||
for (Plugin plugin : pluginManager.getPlugins()) {
|
||||
for (Permission perm : plugin.getDescription().getPermissions()) {
|
||||
try {
|
||||
@@ -2842,7 +2743,7 @@ public final class CraftServer implements Server {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!io.papermc.paper.configuration.GlobalConfiguration.get().misc.loadPermissionsYmlBeforePlugins) loadCustomPermissions();
|
||||
if (!GlobalConfiguration.get().misc.loadPermissionsYmlBeforePlugins) loadCustomPermissions();
|
||||
DefaultPermissions.registerCorePermissions();
|
||||
CraftDefaultPermissions.registerCorePermissions();
|
||||
}
|
||||
@@ -2868,17 +2769,17 @@ public final class CraftServer implements Server {
|
||||
|
||||
@Override
|
||||
public boolean suggestPlayerNamesWhenNullTabCompletions() {
|
||||
return io.papermc.paper.configuration.GlobalConfiguration.get().commands.suggestPlayerNamesWhenNullTabCompletions;
|
||||
return GlobalConfiguration.get().commands.suggestPlayerNamesWhenNullTabCompletions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPermissionMessage() {
|
||||
return net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacyAmpersand().serialize(io.papermc.paper.configuration.GlobalConfiguration.get().messages.noPermission);
|
||||
return LegacyComponentSerializer.legacyAmpersand().serialize(GlobalConfiguration.get().messages.noPermission);
|
||||
}
|
||||
|
||||
@Override
|
||||
public net.kyori.adventure.text.Component permissionMessage() {
|
||||
return io.papermc.paper.configuration.GlobalConfiguration.get().messages.noPermission;
|
||||
return GlobalConfiguration.get().messages.noPermission;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1545,11 +1545,14 @@ public class CraftWorld extends CraftRegionAccessor implements World {
|
||||
}
|
||||
|
||||
public final int getSpawnLimitUnsafe(final SpawnCategory spawnCategory) {
|
||||
int limit = this.spawnCategoryLimit.getOrDefault(spawnCategory, -1);
|
||||
if (limit < 0) {
|
||||
limit = this.server.getSpawnLimitUnsafe(spawnCategory);
|
||||
final int limit = this.spawnCategoryLimit.getOrDefault(spawnCategory, -1);
|
||||
if (limit >= 0) {
|
||||
return limit;
|
||||
}
|
||||
return limit;
|
||||
|
||||
final net.minecraft.world.entity.MobCategory category = CraftSpawnCategory.toNMS(spawnCategory);
|
||||
final int configured = this.world.paperConfig().entities.spawning.spawnLimits.getInt(category);
|
||||
return configured >= 0 ? configured : category.getMaxInstancesPerChunk();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -768,7 +768,7 @@ public class CraftEventFactory {
|
||||
|
||||
// Spigot start - SPIGOT-7523: Merge after spawn event and only merge if the event was not cancelled (gets checked above)
|
||||
if (entity instanceof net.minecraft.world.entity.ExperienceOrb xp) {
|
||||
double radius = level.spigotConfig.expMerge;
|
||||
double radius = level.paperConfig().entities.behavior.mergeRadius.experience;
|
||||
if (radius > 0) {
|
||||
// Paper start - Maximum exp value when merging; Whole section has been tweaked, see comments for specifics
|
||||
final long maxValue = level.paperConfig().entities.behavior.experienceMergeMaxValue;
|
||||
|
||||
@@ -26,7 +26,7 @@ public class RestartCommand extends Command {
|
||||
}
|
||||
|
||||
public static void restart() {
|
||||
RestartCommand.restart(SpigotConfig.restartScript);
|
||||
RestartCommand.restart(io.papermc.paper.configuration.GlobalConfiguration.get().watchdog.restartScript);
|
||||
}
|
||||
|
||||
private static void restart(final String restartScript) {
|
||||
@@ -34,9 +34,9 @@ public class RestartCommand extends Command {
|
||||
// Paper - extract method and cleanup
|
||||
boolean isRestarting = addShutdownHook(restartScript);
|
||||
if (isRestarting) {
|
||||
System.out.println("Attempting to restart with " + SpigotConfig.restartScript);
|
||||
System.out.println("Attempting to restart with " + io.papermc.paper.configuration.GlobalConfiguration.get().watchdog.restartScript);
|
||||
} else {
|
||||
System.out.println("Startup script '" + SpigotConfig.restartScript + "' does not exist! Stopping server.");
|
||||
System.out.println("Startup script '" + io.papermc.paper.configuration.GlobalConfiguration.get().watchdog.restartScript + "' does not exist! Stopping server.");
|
||||
}
|
||||
// Stop the watchdog
|
||||
WatchdogThread.doStop();
|
||||
@@ -53,7 +53,7 @@ public class RestartCommand extends Command {
|
||||
if (MinecraftServer.getServer().isSameThread()) {
|
||||
// Kick all players
|
||||
for (ServerPlayer p : com.google.common.collect.ImmutableList.copyOf(MinecraftServer.getServer().getPlayerList().getPlayers())) {
|
||||
p.connection.disconnect(CraftChatMessage.fromStringOrEmpty(SpigotConfig.restartMessage, true), org.bukkit.event.player.PlayerKickEvent.Cause.RESTART_COMMAND); // Paper - kick event reason (cause is never used)
|
||||
p.connection.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.restart, 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 {
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
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.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import static net.kyori.adventure.text.Component.text;
|
||||
|
||||
public class SpigotCommand extends Command {
|
||||
|
||||
public SpigotCommand(String name) {
|
||||
super(name);
|
||||
this.description = "Spigot related commands";
|
||||
this.usageMessage = "/spigot reload";
|
||||
this.setPermission("bukkit.command.spigot");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean execute(CommandSender sender, String commandLabel, String[] args) {
|
||||
if (!this.testPermission(sender)) return true;
|
||||
|
||||
if (args.length != 1 || !args[0].equals("reload")) {
|
||||
sender.sendMessage(text("Usage: " + this.usageMessage, NamedTextColor.RED));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
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"));
|
||||
for (ServerLevel world : console.getAllLevels()) {
|
||||
world.spigotConfig.init();
|
||||
}
|
||||
console.server.reloadCount++;
|
||||
|
||||
Command.broadcastCommandMessage(sender, text("Reload complete.", NamedTextColor.GREEN));
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,355 +0,0 @@
|
||||
package org.spigotmc;
|
||||
|
||||
import com.google.common.base.Throwables;
|
||||
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.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.world.entity.ai.attributes.Attributes;
|
||||
import net.minecraft.world.entity.ai.attributes.RangedAttribute;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.core.LoggerContext;
|
||||
import org.apache.logging.log4j.core.config.Configuration;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.InvalidConfigurationException;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
|
||||
public class SpigotConfig {
|
||||
|
||||
private static File CONFIG_FILE;
|
||||
private static final String HEADER = """
|
||||
This is the Spigot configuration file for Paper.
|
||||
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.
|
||||
|
||||
If you need help with the configuration or have any questions related to Paper,
|
||||
join us in our Discord or check the docs page.
|
||||
|
||||
File Reference: https://docs.papermc.io/paper/reference/spigot-configuration/
|
||||
Docs: https://docs.papermc.io/
|
||||
Discord: https://discord.gg/papermc
|
||||
Website: https://papermc.io/
|
||||
""";
|
||||
/*========================================================================*/
|
||||
public static YamlConfiguration config;
|
||||
static int version;
|
||||
static Map<String, Command> commands;
|
||||
/*========================================================================*/
|
||||
|
||||
public static void init(File configFile) {
|
||||
SpigotConfig.CONFIG_FILE = configFile;
|
||||
SpigotConfig.config = new YamlConfiguration();
|
||||
try {
|
||||
SpigotConfig.config.load(SpigotConfig.CONFIG_FILE);
|
||||
} 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);
|
||||
}
|
||||
|
||||
SpigotConfig.config.options().header(SpigotConfig.HEADER);
|
||||
SpigotConfig.config.options().copyDefaults(true);
|
||||
|
||||
SpigotConfig.commands = new HashMap<>();
|
||||
SpigotConfig.commands.put("spigot", new SpigotCommand("spigot"));
|
||||
|
||||
SpigotConfig.version = SpigotConfig.getInt("config-version", 13);
|
||||
SpigotConfig.set("config-version", 13);
|
||||
SpigotConfig.readConfig(SpigotConfig.class, null);
|
||||
}
|
||||
|
||||
public static void registerCommands() {
|
||||
for (Map.Entry<String, Command> entry : SpigotConfig.commands.entrySet()) {
|
||||
MinecraftServer.getServer().server.getCommandMap().register(entry.getKey(), "Spigot", entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
public 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 {
|
||||
SpigotConfig.config.save(SpigotConfig.CONFIG_FILE);
|
||||
} catch (IOException ex) {
|
||||
Bukkit.getLogger().log(Level.SEVERE, "Could not save " + SpigotConfig.CONFIG_FILE, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static void set(String path, Object val) {
|
||||
SpigotConfig.config.set(path, val);
|
||||
}
|
||||
|
||||
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) {
|
||||
SpigotConfig.config.addDefault(path, def);
|
||||
return SpigotConfig.config.getInt(path, SpigotConfig.config.getInt(path));
|
||||
}
|
||||
|
||||
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) {
|
||||
SpigotConfig.config.addDefault(path, def);
|
||||
return SpigotConfig.config.getString(path, SpigotConfig.config.getString(path));
|
||||
}
|
||||
|
||||
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() {
|
||||
SpigotConfig.logCommands = SpigotConfig.getBoolean("commands.log", true);
|
||||
}
|
||||
|
||||
public static int tabComplete;
|
||||
public static boolean sendNamespaced;
|
||||
private static void tabComplete() {
|
||||
if (SpigotConfig.version < 6) {
|
||||
boolean oldValue = SpigotConfig.getBoolean("commands.tab-complete", true);
|
||||
if (oldValue) {
|
||||
SpigotConfig.set("commands.tab-complete", 0);
|
||||
} else {
|
||||
SpigotConfig.set("commands.tab-complete", -1);
|
||||
}
|
||||
}
|
||||
SpigotConfig.tabComplete = SpigotConfig.getInt("commands.tab-complete", 0);
|
||||
SpigotConfig.sendNamespaced = SpigotConfig.getBoolean("commands.send-namespaced", true);
|
||||
}
|
||||
|
||||
public static String whitelistMessage;
|
||||
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) {
|
||||
return ChatColor.translateAlternateColorCodes('&', s).replaceAll("\\\\n", "\n");
|
||||
}
|
||||
|
||||
private static void messages() {
|
||||
if (SpigotConfig.version < 8) {
|
||||
SpigotConfig.set("messages.outdated-client", SpigotConfig.outdatedClientMessage);
|
||||
SpigotConfig.set("messages.outdated-server", SpigotConfig.outdatedServerMessage);
|
||||
}
|
||||
|
||||
SpigotConfig.whitelistMessage = SpigotConfig.transform(SpigotConfig.getString("messages.whitelist", "You are not whitelisted on this server!"));
|
||||
SpigotConfig.unknownCommandMessage = SpigotConfig.transform(SpigotConfig.getString("messages.unknown-command", "Unknown command. Type \"/help\" for help."));
|
||||
SpigotConfig.serverFullMessage = SpigotConfig.transform(SpigotConfig.getString("messages.server-full", "The server is full!"));
|
||||
SpigotConfig.outdatedClientMessage = SpigotConfig.transform(SpigotConfig.getString("messages.outdated-client", SpigotConfig.outdatedClientMessage));
|
||||
SpigotConfig.outdatedServerMessage = SpigotConfig.transform(SpigotConfig.getString("messages.outdated-server", SpigotConfig.outdatedServerMessage));
|
||||
}
|
||||
|
||||
public static int timeoutTime = 60;
|
||||
public static boolean restartOnCrash = true;
|
||||
public static String restartScript = "./start.sh";
|
||||
public static String restartMessage;
|
||||
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"));
|
||||
}
|
||||
|
||||
public static boolean bungee;
|
||||
private static void bungee() {
|
||||
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() {
|
||||
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);
|
||||
}
|
||||
|
||||
public static boolean disableStatSaving;
|
||||
public static Map<Identifier, Integer> forcedStats = new HashMap<>();
|
||||
|
||||
private static void stats() {
|
||||
SpigotConfig.disableStatSaving = SpigotConfig.getBoolean("stats.disable-saving", false);
|
||||
|
||||
if (!SpigotConfig.config.contains("stats.forced-stats")) {
|
||||
SpigotConfig.config.createSection("stats.forced-stats");
|
||||
}
|
||||
|
||||
ConfigurationSection section = SpigotConfig.config.getConfigurationSection("stats.forced-stats");
|
||||
for (String name : section.getKeys(true)) {
|
||||
if (section.isInt(name)) {
|
||||
try {
|
||||
Identifier key = Identifier.parse(name);
|
||||
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) {
|
||||
Bukkit.getLogger().log(Level.WARNING, "Ignoring invalid stats.forced-stats " + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void tpsCommand() {
|
||||
SpigotConfig.commands.put("tps", new TicksPerSecondCommand("tps"));
|
||||
}
|
||||
|
||||
public static int 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() {
|
||||
SpigotConfig.playerShuffle = SpigotConfig.getInt("settings.player-shuffle", 0);
|
||||
}
|
||||
|
||||
public static boolean enableSpamExclusions = false;
|
||||
public static List<String> spamExclusions;
|
||||
private static void spamExclusions() {
|
||||
SpigotConfig.spamExclusions = SpigotConfig.getList("commands.spam-exclusions", List.of("/skill"));
|
||||
Object enabled = SpigotConfig.config.get("commands.enable-spam-exclusions");
|
||||
if (enabled instanceof Boolean value) {
|
||||
SpigotConfig.enableSpamExclusions = value;
|
||||
} else {
|
||||
if (spamExclusions.size() == 1 && Objects.equals(spamExclusions.getFirst(), "/skill")) {
|
||||
SpigotConfig.enableSpamExclusions = false;
|
||||
SpigotConfig.set("commands.enable-spam-exclusions", false);
|
||||
} else {
|
||||
SpigotConfig.enableSpamExclusions = true;
|
||||
SpigotConfig.set("commands.enable-spam-exclusions", true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static boolean 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")) {
|
||||
SpigotConfig.set("commands.replace-commands", SpigotConfig.config.getStringList("replace-commands"));
|
||||
SpigotConfig.config.set("replace-commands", null);
|
||||
}
|
||||
SpigotConfig.replaceCommands = new HashSet<>(SpigotConfig.getList("commands.replace-commands",
|
||||
Arrays.asList("setblock", "summon", "testforblock", "tellraw")));
|
||||
}
|
||||
|
||||
public static int userCacheCap;
|
||||
private static void userCacheCap() {
|
||||
SpigotConfig.userCacheCap = SpigotConfig.getInt("settings.user-cache-size", 1000);
|
||||
}
|
||||
|
||||
public static boolean 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() {
|
||||
SpigotConfig.movedWronglyThreshold = SpigotConfig.getDouble("settings.moved-wrongly-threshold", 0.0625);
|
||||
}
|
||||
|
||||
public static double movedTooQuicklyMultiplier;
|
||||
private static void movedTooQuicklyMultiplier() {
|
||||
SpigotConfig.movedTooQuicklyMultiplier = SpigotConfig.getDouble("settings.moved-too-quickly-multiplier", 10.0);
|
||||
}
|
||||
|
||||
public static double maxAbsorption = 2048;
|
||||
public static double maxHealth = 1024;
|
||||
public static double movementSpeed = 1024;
|
||||
public static double attackDamage = 2048;
|
||||
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);
|
||||
((RangedAttribute) Attributes.MAX_HEALTH.value()).maxValue = SpigotConfig.maxHealth;
|
||||
SpigotConfig.movementSpeed = SpigotConfig.getDouble("settings.attribute.movementSpeed.max", SpigotConfig.movementSpeed);
|
||||
((RangedAttribute) Attributes.MOVEMENT_SPEED.value()).maxValue = SpigotConfig.movementSpeed;
|
||||
SpigotConfig.attackDamage = SpigotConfig.getDouble("settings.attribute.attackDamage.max", SpigotConfig.attackDamage);
|
||||
((RangedAttribute) Attributes.ATTACK_DAMAGE.value()).maxValue = SpigotConfig.attackDamage;
|
||||
}
|
||||
|
||||
public static boolean debug;
|
||||
private static void debug() {
|
||||
SpigotConfig.debug = SpigotConfig.getBoolean("settings.debug", false);
|
||||
|
||||
if (SpigotConfig.debug && !LogManager.getRootLogger().isTraceEnabled()) {
|
||||
// Enable debug logging
|
||||
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
|
||||
Configuration conf = ctx.getConfiguration();
|
||||
conf.getLoggerConfig(LogManager.ROOT_LOGGER_NAME).setLevel(org.apache.logging.log4j.Level.ALL);
|
||||
ctx.updateLoggers(conf);
|
||||
}
|
||||
|
||||
if (LogManager.getRootLogger().isTraceEnabled()) {
|
||||
Bukkit.getLogger().info("Debug logging is enabled");
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean disableAdvancementSaving;
|
||||
public static List<String> disabledAdvancements;
|
||||
private static void disabledAdvancements() {
|
||||
SpigotConfig.disableAdvancementSaving = SpigotConfig.getBoolean("advancements.disable-saving", false);
|
||||
SpigotConfig.disabledAdvancements = SpigotConfig.getList("advancements.disabled", List.of("minecraft:story/disabled"));
|
||||
}
|
||||
|
||||
public static boolean logVillagerDeaths;
|
||||
public static boolean logNamedDeaths;
|
||||
private static void logDeaths() {
|
||||
SpigotConfig.logVillagerDeaths = SpigotConfig.getBoolean("settings.log-villager-deaths", true);
|
||||
SpigotConfig.logNamedDeaths = SpigotConfig.getBoolean("settings.log-named-deaths", true);
|
||||
}
|
||||
|
||||
public static boolean disablePlayerDataSaving;
|
||||
private static void disablePlayerDataSaving() {
|
||||
SpigotConfig.disablePlayerDataSaving = SpigotConfig.getBoolean("players.disable-saving", false);
|
||||
}
|
||||
|
||||
public static boolean belowZeroGenerationInExistingChunks;
|
||||
private static void belowZeroGenerationInExistingChunks() {
|
||||
SpigotConfig.belowZeroGenerationInExistingChunks = SpigotConfig.getBoolean("world-settings.default.below-zero-generation-in-existing-chunks", true);
|
||||
}
|
||||
}
|
||||
@@ -1,472 +0,0 @@
|
||||
package org.spigotmc;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import net.kyori.adventure.key.Key;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
|
||||
public class SpigotWorldConfig {
|
||||
|
||||
private final String legacyWorldName;
|
||||
private String worldName;
|
||||
private final YamlConfiguration config;
|
||||
private boolean verbose;
|
||||
|
||||
public SpigotWorldConfig(String legacyWorldName, Key worldKey) {
|
||||
this.legacyWorldName = legacyWorldName;
|
||||
this.worldName = worldKey.asString();
|
||||
this.config = SpigotConfig.config;
|
||||
this.init();
|
||||
}
|
||||
|
||||
public void init() {
|
||||
this.verbose = this.getBoolean("verbose", false); // Paper
|
||||
if (SpigotConfig.version <= 12) {
|
||||
ConfigurationSection section = this.config.getConfigurationSection("world-settings." + this.legacyWorldName);
|
||||
if (section != null) {
|
||||
this.config.set("world-settings." + this.legacyWorldName, null);
|
||||
this.config.set("world-settings." + this.worldName, section);
|
||||
Bukkit.getLogger().info("NOTE: Migrated spigot world config %s -> %s".formatted(this.legacyWorldName, this.worldName));
|
||||
}
|
||||
}
|
||||
|
||||
this.log("-------- World Settings For [" + this.worldName + "] --------");
|
||||
SpigotConfig.readConfig(SpigotWorldConfig.class, this);
|
||||
}
|
||||
|
||||
private void log(String s) {
|
||||
if (this.verbose) {
|
||||
Bukkit.getLogger().info(s);
|
||||
}
|
||||
}
|
||||
|
||||
private void set(String path, Object val) {
|
||||
this.config.set("world-settings.default." + path, val);
|
||||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
return this.config.getInt("world-settings." + this.worldName + "." + path);
|
||||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
this.config.addDefault("world-settings.default." + path, def);
|
||||
return this.config.get("world-settings." + this.worldName + "." + path, this.config.get("world-settings.default." + path));
|
||||
}
|
||||
|
||||
// Crop growth rates
|
||||
public int cactusModifier;
|
||||
public int caneModifier;
|
||||
public int melonModifier;
|
||||
public int mushroomModifier;
|
||||
public int pumpkinModifier;
|
||||
public int saplingModifier;
|
||||
public int beetrootModifier;
|
||||
public int carrotModifier;
|
||||
public int potatoModifier;
|
||||
public int torchFlowerModifier;
|
||||
public int wheatModifier;
|
||||
public int wartModifier;
|
||||
public int vineModifier;
|
||||
public int cocoaModifier;
|
||||
public int bambooModifier;
|
||||
public int sweetBerryModifier;
|
||||
public int kelpModifier;
|
||||
public int twistingVinesModifier;
|
||||
public int weepingVinesModifier;
|
||||
public int caveVinesModifier;
|
||||
public int glowBerryModifier;
|
||||
public int pitcherPlantModifier;
|
||||
|
||||
private int getAndValidateGrowth(String crop) {
|
||||
int modifier = this.getInt("growth." + crop.toLowerCase(java.util.Locale.ENGLISH) + "-modifier", 100);
|
||||
if (modifier == 0) {
|
||||
this.log("Cannot set " + crop + " growth to zero, defaulting to 100");
|
||||
modifier = 100;
|
||||
}
|
||||
this.log(crop + " Growth Modifier: " + modifier + "%");
|
||||
|
||||
return modifier;
|
||||
}
|
||||
|
||||
private void growthModifiers() {
|
||||
this.cactusModifier = this.getAndValidateGrowth("Cactus");
|
||||
this.caneModifier = this.getAndValidateGrowth("Cane");
|
||||
this.melonModifier = this.getAndValidateGrowth("Melon");
|
||||
this.mushroomModifier = this.getAndValidateGrowth("Mushroom");
|
||||
this.pumpkinModifier = this.getAndValidateGrowth("Pumpkin");
|
||||
this.saplingModifier = this.getAndValidateGrowth("Sapling");
|
||||
this.beetrootModifier = this.getAndValidateGrowth("Beetroot");
|
||||
this.carrotModifier = this.getAndValidateGrowth("Carrot");
|
||||
this.potatoModifier = this.getAndValidateGrowth("Potato");
|
||||
this.torchFlowerModifier = this.getAndValidateGrowth("TorchFlower");
|
||||
this.wheatModifier = this.getAndValidateGrowth("Wheat");
|
||||
this.wartModifier = this.getAndValidateGrowth("NetherWart");
|
||||
this.vineModifier = this.getAndValidateGrowth("Vine");
|
||||
this.cocoaModifier = this.getAndValidateGrowth("Cocoa");
|
||||
this.bambooModifier = this.getAndValidateGrowth("Bamboo");
|
||||
this.sweetBerryModifier = this.getAndValidateGrowth("SweetBerry");
|
||||
this.kelpModifier = this.getAndValidateGrowth("Kelp");
|
||||
this.twistingVinesModifier = this.getAndValidateGrowth("TwistingVines");
|
||||
this.weepingVinesModifier = this.getAndValidateGrowth("WeepingVines");
|
||||
this.caveVinesModifier = this.getAndValidateGrowth("CaveVines");
|
||||
this.glowBerryModifier = this.getAndValidateGrowth("GlowBerry");
|
||||
this.pitcherPlantModifier = this.getAndValidateGrowth("PitcherPlant");
|
||||
}
|
||||
|
||||
public double 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() {
|
||||
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) {
|
||||
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) {
|
||||
this.viewDistance = Bukkit.getViewDistance();
|
||||
}
|
||||
|
||||
this.viewDistance = Math.max(Math.min(this.viewDistance, 32), 3);
|
||||
this.log("View Distance: " + this.viewDistance);
|
||||
}
|
||||
|
||||
public int simulationDistance;
|
||||
private void simulationDistance() {
|
||||
Object simulationDistanceObject = this.get("simulation-distance", "default");
|
||||
this.simulationDistance = (simulationDistanceObject) instanceof Number ? ((Number) simulationDistanceObject).intValue() : -1;
|
||||
if (this.simulationDistance <= 0) {
|
||||
this.simulationDistance = Bukkit.getSimulationDistance();
|
||||
}
|
||||
|
||||
this.log("Simulation Distance: " + this.simulationDistance);
|
||||
}
|
||||
|
||||
public byte 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() {
|
||||
this.itemDespawnRate = this.getInt("item-despawn-rate", 6000);
|
||||
this.log("Item Despawn Rate: " + this.itemDespawnRate);
|
||||
}
|
||||
|
||||
public int animalActivationRange = 32;
|
||||
public int monsterActivationRange = 32;
|
||||
public int raiderActivationRange = 64;
|
||||
public int miscActivationRange = 16;
|
||||
public int flyingMonsterActivationRange = 32;
|
||||
public int waterActivationRange = 16;
|
||||
public int villagerActivationRange = 32;
|
||||
public int wakeUpInactiveAnimals = 4;
|
||||
public int wakeUpInactiveAnimalsEvery = 60 * 20;
|
||||
public int wakeUpInactiveAnimalsFor = 5 * 20;
|
||||
public int wakeUpInactiveMonsters = 8;
|
||||
public int wakeUpInactiveMonstersEvery = 20 * 20;
|
||||
public int wakeUpInactiveMonstersFor = 5 * 20;
|
||||
public int wakeUpInactiveVillagers = 4;
|
||||
public int wakeUpInactiveVillagersEvery = 30 * 20;
|
||||
public int wakeUpInactiveVillagersFor = 5 * 20;
|
||||
public int wakeUpInactiveFlying = 8;
|
||||
public int wakeUpInactiveFlyingEvery = 10 * 20;
|
||||
public int wakeUpInactiveFlyingFor = 5 * 20;
|
||||
public int villagersWorkImmunityAfter = 5 * 20;
|
||||
public int villagersWorkImmunityFor = 20;
|
||||
public boolean villagersActiveForPanic = true;
|
||||
public boolean tickInactiveVillagers = true;
|
||||
public boolean ignoreSpectatorActivation = false;
|
||||
|
||||
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);
|
||||
this.raiderActivationRange = this.getInt("entity-activation-range.raiders", this.raiderActivationRange);
|
||||
this.miscActivationRange = this.getInt("entity-activation-range.misc", this.miscActivationRange);
|
||||
this.waterActivationRange = this.getInt("entity-activation-range.water", this.waterActivationRange);
|
||||
this.villagerActivationRange = this.getInt("entity-activation-range.villagers", hasAnimalsConfig ? this.animalActivationRange : this.villagerActivationRange);
|
||||
this.flyingMonsterActivationRange = this.getInt("entity-activation-range.flying-monsters", this.flyingMonsterActivationRange);
|
||||
|
||||
this.wakeUpInactiveAnimals = this.getInt("entity-activation-range.wake-up-inactive.animals-max-per-tick", this.wakeUpInactiveAnimals);
|
||||
this.wakeUpInactiveAnimalsEvery = this.getInt("entity-activation-range.wake-up-inactive.animals-every", this.wakeUpInactiveAnimalsEvery);
|
||||
this.wakeUpInactiveAnimalsFor = this.getInt("entity-activation-range.wake-up-inactive.animals-for", this.wakeUpInactiveAnimalsFor);
|
||||
|
||||
this.wakeUpInactiveMonsters = this.getInt("entity-activation-range.wake-up-inactive.monsters-max-per-tick", this.wakeUpInactiveMonsters);
|
||||
this.wakeUpInactiveMonstersEvery = this.getInt("entity-activation-range.wake-up-inactive.monsters-every", this.wakeUpInactiveMonstersEvery);
|
||||
this.wakeUpInactiveMonstersFor = this.getInt("entity-activation-range.wake-up-inactive.monsters-for", this.wakeUpInactiveMonstersFor);
|
||||
|
||||
this.wakeUpInactiveVillagers = this.getInt("entity-activation-range.wake-up-inactive.villagers-max-per-tick", this.wakeUpInactiveVillagers);
|
||||
this.wakeUpInactiveVillagersEvery = this.getInt("entity-activation-range.wake-up-inactive.villagers-every", this.wakeUpInactiveVillagersEvery);
|
||||
this.wakeUpInactiveVillagersFor = this.getInt("entity-activation-range.wake-up-inactive.villagers-for", this.wakeUpInactiveVillagersFor);
|
||||
|
||||
this.wakeUpInactiveFlying = this.getInt("entity-activation-range.wake-up-inactive.flying-monsters-max-per-tick", this.wakeUpInactiveFlying);
|
||||
this.wakeUpInactiveFlyingEvery = this.getInt("entity-activation-range.wake-up-inactive.flying-monsters-every", this.wakeUpInactiveFlyingEvery);
|
||||
this.wakeUpInactiveFlyingFor = this.getInt("entity-activation-range.wake-up-inactive.flying-monsters-for", this.wakeUpInactiveFlyingFor);
|
||||
|
||||
this.villagersWorkImmunityAfter = this.getInt("entity-activation-range.villagers-work-immunity-after", this.villagersWorkImmunityAfter);
|
||||
this.villagersWorkImmunityFor = this.getInt("entity-activation-range.villagers-work-immunity-for", this.villagersWorkImmunityFor);
|
||||
this.villagersActiveForPanic = this.getBoolean("entity-activation-range.villagers-active-for-panic", this.villagersActiveForPanic);
|
||||
this.tickInactiveVillagers = this.getBoolean("entity-activation-range.tick-inactive-villagers", this.tickInactiveVillagers);
|
||||
this.ignoreSpectatorActivation = this.getBoolean("entity-activation-range.ignore-spectators", this.ignoreSpectatorActivation);
|
||||
this.log("Entity Activation Range: An " + this.animalActivationRange + " / Mo " + this.monsterActivationRange + " / Ra " + this.raiderActivationRange + " / Mi " + this.miscActivationRange + " / Tiv " + this.tickInactiveVillagers + " / Isa " + this.ignoreSpectatorActivation);
|
||||
}
|
||||
|
||||
public int playerTrackingRange = 128;
|
||||
public int animalTrackingRange = 96;
|
||||
public int monsterTrackingRange = 96;
|
||||
public int miscTrackingRange = 96;
|
||||
public int displayTrackingRange = 128;
|
||||
public int otherTrackingRange = 64;
|
||||
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);
|
||||
this.miscTrackingRange = this.getInt("entity-tracking-range.misc", this.miscTrackingRange);
|
||||
this.displayTrackingRange = this.getInt("entity-tracking-range.display", this.displayTrackingRange);
|
||||
this.otherTrackingRange = this.getInt("entity-tracking-range.other", this.otherTrackingRange);
|
||||
this.log("Entity Tracking Range: Pl " + this.playerTrackingRange + " / An " + this.animalTrackingRange + " / Mo " + this.monsterTrackingRange + " / Mi " + this.miscTrackingRange + " / Di " + this.displayTrackingRange + " / Other " + this.otherTrackingRange);
|
||||
}
|
||||
|
||||
public int hopperTransfer;
|
||||
public int hopperCheck;
|
||||
public int hopperAmount;
|
||||
public boolean hopperCanLoadChunks;
|
||||
private void hoppers() {
|
||||
// Set the tick delay between hopper item movements
|
||||
this.hopperTransfer = this.getInt("ticks-per.hopper-transfer", 8);
|
||||
if (SpigotConfig.version < 11) {
|
||||
this.set("ticks-per.hopper-check", 1);
|
||||
}
|
||||
this.hopperCheck = this.getInt("ticks-per.hopper-check", 1);
|
||||
this.hopperAmount = this.getInt("hopper-amount", 1);
|
||||
this.hopperCanLoadChunks = this.getBoolean("hopper-can-load-chunks", false);
|
||||
this.log("Hopper Transfer: " + this.hopperTransfer + " Hopper Check: " + this.hopperCheck + " Hopper Amount: " + this.hopperAmount + " Hopper Can Load Chunks: " + this.hopperCanLoadChunks);
|
||||
}
|
||||
|
||||
public int arrowDespawnRate;
|
||||
public int tridentDespawnRate;
|
||||
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 Despawn Rate:" + this.tridentDespawnRate);
|
||||
}
|
||||
|
||||
public boolean 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() {
|
||||
this.nerfSpawnerMobs = this.getBoolean("nerf-spawner-mobs", false);
|
||||
this.log("Nerfing mobs spawned from spawners: " + this.nerfSpawnerMobs);
|
||||
}
|
||||
|
||||
public boolean 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() {
|
||||
this.dragonDeathSoundRadius = this.getInt("dragon-death-sound-radius", 0);
|
||||
}
|
||||
|
||||
public int witherSpawnSoundRadius;
|
||||
private void witherSpawnSoundRadius() {
|
||||
this.witherSpawnSoundRadius = this.getInt("wither-spawn-sound-radius", 0);
|
||||
}
|
||||
|
||||
public int endPortalSoundRadius;
|
||||
private void endPortalSoundRadius() {
|
||||
this.endPortalSoundRadius = this.getInt("end-portal-sound-radius", 0);
|
||||
}
|
||||
|
||||
public int villageSeed;
|
||||
public int desertSeed;
|
||||
public int iglooSeed;
|
||||
public int jungleSeed;
|
||||
public int swampSeed;
|
||||
public int monumentSeed;
|
||||
public int oceanSeed;
|
||||
public int outpostSeed;
|
||||
public int shipwreckSeed;
|
||||
public int endCitySeed;
|
||||
public int netherSeed;
|
||||
public int mansionSeed;
|
||||
public int fossilSeed;
|
||||
public int portalSeed;
|
||||
public int ancientCitySeed;
|
||||
public int trailRuinsSeed;
|
||||
public int trialChambersSeed;
|
||||
public int abandonedCampSeed;
|
||||
|
||||
public int buriedTreasureSeed;
|
||||
public Integer mineshaftSeed;
|
||||
public Long strongholdSeed;
|
||||
public int slimeSeed;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private void initWorldGenSeeds() {
|
||||
this.villageSeed = this.getInt("seed-village", 10387312);
|
||||
this.desertSeed = this.getInt("seed-desert", 14357617);
|
||||
this.iglooSeed = this.getInt("seed-igloo", 14357618);
|
||||
this.jungleSeed = this.getInt("seed-jungle", 14357619);
|
||||
this.swampSeed = this.getInt("seed-swamp", 14357620);
|
||||
this.monumentSeed = this.getInt("seed-monument", 10387313);
|
||||
this.shipwreckSeed = this.getInt("seed-shipwreck", 165745295);
|
||||
this.oceanSeed = this.getInt("seed-ocean", 14357621);
|
||||
this.outpostSeed = this.getInt("seed-outpost", 165745296);
|
||||
this.endCitySeed = this.getInt("seed-endcity", 10387313);
|
||||
this.netherSeed = this.getInt("seed-nether", 30084232);
|
||||
this.mansionSeed = this.getInt("seed-mansion", 10387319);
|
||||
this.fossilSeed = this.getInt("seed-fossil", 14357921);
|
||||
this.portalSeed = this.getInt("seed-portal", 34222645);
|
||||
this.ancientCitySeed = this.getInt("seed-ancientcity", 20083232);
|
||||
this.trailRuinsSeed = this.getInt("seed-trailruins", 83469867);
|
||||
this.trialChambersSeed = this.getInt("seed-trialchambers", 94251327);
|
||||
this.abandonedCampSeed = this.getInt("seed-abandonedcamp", 91231127);
|
||||
|
||||
this.buriedTreasureSeed = this.getInt("seed-buriedtreasure", 10387320); // AbstractSpreadingStructurePlacement#HIGHLY_ARBITRARY_RANDOM_SALT
|
||||
this.mineshaftSeed = this.getSeed("seed-mineshaft", Integer::parseInt);
|
||||
this.strongholdSeed = this.getSeed("seed-stronghold", Long::parseLong);
|
||||
this.slimeSeed = this.getInt("seed-slime", 987234911);
|
||||
this.log("Custom Map Seeds: " +
|
||||
" Village: " + this.villageSeed +
|
||||
" Desert: " + this.desertSeed +
|
||||
" Igloo: " + this.iglooSeed +
|
||||
" Jungle: " + this.jungleSeed +
|
||||
" Swamp: " + this.swampSeed +
|
||||
" Monument: " + this.monumentSeed +
|
||||
" Shipwreck: " + this.shipwreckSeed +
|
||||
" Ocean: " + this.oceanSeed +
|
||||
" Outpost: " + this.outpostSeed +
|
||||
" End City: " + this.endCitySeed +
|
||||
" Nether: " + this.netherSeed +
|
||||
" Mansion: " + this.mansionSeed +
|
||||
" Fossil: " + this.fossilSeed +
|
||||
" Portal: " + this.portalSeed +
|
||||
" Ancient City: " + this.ancientCitySeed +
|
||||
" Trail Ruins: " + this.trailRuinsSeed +
|
||||
" Trial Chambers: " + this.trialChambersSeed +
|
||||
" Abandoned Camp: " + this.abandonedCampSeed +
|
||||
" Buried Treasure: " + this.buriedTreasureSeed +
|
||||
" Mineshaft: " + this.mineshaftSeed +
|
||||
" Stronghold: " + this.strongholdSeed +
|
||||
" Slime: " + this.slimeSeed
|
||||
);
|
||||
}
|
||||
|
||||
public float jumpWalkExhaustion;
|
||||
public float jumpSprintExhaustion;
|
||||
public float combatExhaustion;
|
||||
public float regenExhaustion;
|
||||
public float swimMultiplier;
|
||||
public float sprintMultiplier;
|
||||
public float otherMultiplier;
|
||||
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);
|
||||
this.set("hunger.regen-exhaustion", 6.0);
|
||||
}
|
||||
|
||||
this.jumpWalkExhaustion = (float) this.getDouble("hunger.jump-walk-exhaustion", 0.05);
|
||||
this.jumpSprintExhaustion = (float) this.getDouble("hunger.jump-sprint-exhaustion", 0.2);
|
||||
this.combatExhaustion = (float) this.getDouble("hunger.combat-exhaustion", 0.1);
|
||||
this.regenExhaustion = (float) this.getDouble("hunger.regen-exhaustion", 6.0);
|
||||
this.swimMultiplier = (float) this.getDouble("hunger.swim-multiplier", 0.01);
|
||||
this.sprintMultiplier = (float) this.getDouble("hunger.sprint-multiplier", 0.1);
|
||||
this.otherMultiplier = (float) this.getDouble("hunger.other-multiplier", 0.0);
|
||||
}
|
||||
|
||||
public int currentPrimedTnt = 0;
|
||||
public int maxTntTicksPerTick;
|
||||
private void maxTntPerTick() {
|
||||
if (SpigotConfig.version < 7) {
|
||||
this.set("max-tnt-per-tick", 100);
|
||||
}
|
||||
this.maxTntTicksPerTick = this.getInt("max-tnt-per-tick", 100);
|
||||
this.log("Max TNT Explosions: " + this.maxTntTicksPerTick);
|
||||
}
|
||||
|
||||
public int hangingTickFrequency;
|
||||
private void hangingTickFrequency() {
|
||||
this.hangingTickFrequency = this.getInt("hanging-tick-frequency", 100);
|
||||
}
|
||||
|
||||
public int tileMaxTickTime;
|
||||
public int entityMaxTickTime;
|
||||
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() {
|
||||
this.thunderChance = this.getInt("thunder-chance", 100000);
|
||||
}
|
||||
|
||||
public boolean belowZeroGenerationInExistingChunks;
|
||||
private void belowZeroGenerationInExistingChunks() {
|
||||
this.belowZeroGenerationInExistingChunks = this.getBoolean("below-zero-generation-in-existing-chunks", true);
|
||||
}
|
||||
|
||||
public boolean unloadFrozenChunks;
|
||||
private void unloadFrozenChunks() {
|
||||
this.unloadFrozenChunks = this.getBoolean("unload-frozen-chunks", false);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package org.spigotmc;
|
||||
|
||||
public class TickLimiter {
|
||||
|
||||
private final int maxTime;
|
||||
private long startTime;
|
||||
|
||||
public TickLimiter(int maxTime) {
|
||||
this.maxTime = maxTime;
|
||||
}
|
||||
|
||||
public void initTick() {
|
||||
this.startTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public boolean shouldContinue() {
|
||||
long remaining = System.currentTimeMillis() - this.startTime;
|
||||
return remaining < this.maxTime;
|
||||
}
|
||||
}
|
||||
@@ -25,9 +25,9 @@ public final class TrackingRange {
|
||||
return defaultRange;
|
||||
}
|
||||
|
||||
final SpigotWorldConfig config = entity.level().spigotConfig;
|
||||
final io.papermc.paper.configuration.WorldConfiguration config = entity.level().paperConfig();
|
||||
if (entity instanceof ServerPlayer) {
|
||||
return config.playerTrackingRange;
|
||||
return config.entities.trackingRange.player;
|
||||
}
|
||||
|
||||
if (entity instanceof net.minecraft.world.entity.boss.enderdragon.EnderDragon) {
|
||||
@@ -39,20 +39,20 @@ public final class TrackingRange {
|
||||
case RAIDER:
|
||||
case MONSTER:
|
||||
case FLYING_MONSTER:
|
||||
return config.monsterTrackingRange;
|
||||
return config.entities.trackingRange.monster;
|
||||
case WATER:
|
||||
case VILLAGER:
|
||||
case ANIMAL:
|
||||
return config.animalTrackingRange;
|
||||
return config.entities.trackingRange.animal;
|
||||
case MISC:
|
||||
}
|
||||
|
||||
if (entity instanceof ItemFrame || entity instanceof Painting || entity instanceof ItemEntity || entity instanceof ExperienceOrb) {
|
||||
return config.miscTrackingRange;
|
||||
return config.entities.trackingRange.misc;
|
||||
} else if (entity instanceof Display) {
|
||||
return config.displayTrackingRange;
|
||||
return config.entities.trackingRange.display;
|
||||
} else {
|
||||
return config.otherTrackingRange;
|
||||
return config.entities.trackingRange.other;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,7 +79,7 @@ public class WatchdogThread extends ca.spottedleaf.moonrise.common.util.TickThre
|
||||
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, "\t If this is the case, consider increasing watchdog.timeout-seconds in paper-global.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());
|
||||
@@ -129,7 +129,7 @@ public class WatchdogThread extends ca.spottedleaf.moonrise.common.util.TickThre
|
||||
if (!server.hasStopped()) {
|
||||
server.forceTicks = true;
|
||||
if (this.restart) {
|
||||
RestartCommand.addShutdownHook(SpigotConfig.restartScript);
|
||||
RestartCommand.addShutdownHook(io.papermc.paper.configuration.GlobalConfiguration.get().watchdog.restartScript);
|
||||
}
|
||||
// try one last chance to safe shutdown on main in case it 'comes back'
|
||||
server.abnormalExit = true;
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
# This is the Bukkit configuration file in Paper.
|
||||
# As you can see, there's actually not that much to configure without any plugins.
|
||||
#
|
||||
# If you need help with the configuration or have any questions related to Paper,
|
||||
# join us in our Discord or check the docs page.
|
||||
#
|
||||
# File Reference: https://docs.papermc.io/paper/reference/bukkit-configuration/
|
||||
# Docs: https://docs.papermc.io/
|
||||
# Discord: https://discord.gg/papermc
|
||||
# Website: https://papermc.io/
|
||||
|
||||
settings:
|
||||
allow-end: true
|
||||
warn-on-overload: true
|
||||
permissions-file: permissions.yml
|
||||
update-folder: update
|
||||
plugin-profiling: false
|
||||
connection-throttle: 4000
|
||||
query-plugins: true
|
||||
deprecated-verbose: default
|
||||
shutdown-message: Server closed
|
||||
minimum-api: none
|
||||
use-map-color-cache: true
|
||||
spawn-limits:
|
||||
monsters: 70
|
||||
animals: 10
|
||||
water-animals: 5
|
||||
water-ambient: 20
|
||||
water-underground-creature: 5
|
||||
axolotls: 5
|
||||
ambient: 15
|
||||
chunk-gc:
|
||||
period-in-ticks: 600
|
||||
ticks-per:
|
||||
animal-spawns: 400
|
||||
monster-spawns: 1
|
||||
water-spawns: 1
|
||||
water-ambient-spawns: 1
|
||||
water-underground-creature-spawns: 1
|
||||
axolotl-spawns: 1
|
||||
ambient-spawns: 1
|
||||
autosave: 6000
|
||||
aliases: now-in-commands.yml
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
package io.papermc.paper.configuration;
|
||||
|
||||
import io.papermc.paper.configuration.mapping.LoadEarly;
|
||||
import io.papermc.paper.configuration.yaml.YamlConfigurationLoader;
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
import joptsimple.OptionParser;
|
||||
import org.bukkit.support.environment.Normal;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
/**
|
||||
* Makes sure the early global config load, which runs before {@code Bootstrap}, never touches anything that requires it.
|
||||
* Test environments are always bootstrapped, so the load runs in a separate JVM that never bootstraps.
|
||||
* Fails if a {@link LoadEarly} field, or any field initializer of a section containing one, requires the game to be bootstrapped.
|
||||
*/
|
||||
@Normal
|
||||
public class EarlyGlobalConfigPreBootstrapTest {
|
||||
|
||||
private static final String DONE = "early global config loaded";
|
||||
private static final String CLASS_INIT_LOG = "[class,init]";
|
||||
// Initializing either of these before Bootstrap#bootStrap means registries were accessed
|
||||
private static final List<String> BOOTSTRAP_CLASSES = List.of(
|
||||
"net/minecraft/server/Bootstrap",
|
||||
"net/minecraft/core/registries/BuiltInRegistries"
|
||||
);
|
||||
|
||||
@TempDir
|
||||
Path dir;
|
||||
|
||||
@Test
|
||||
public void earlyLoadDoesNotRequireBootstrap() throws Exception {
|
||||
final Path outputFile = this.dir.resolve("output.txt");
|
||||
final Process process = new ProcessBuilder(
|
||||
Path.of(System.getProperty("java.home"), "bin", "java").toString(),
|
||||
"-Xlog:class+init=info:stdout",
|
||||
"-cp", System.getProperty("java.class.path"),
|
||||
EarlyLoad.class.getName(),
|
||||
this.dir.toString()
|
||||
)
|
||||
.directory(this.dir.toFile())
|
||||
.redirectErrorStream(true)
|
||||
.redirectOutput(outputFile.toFile())
|
||||
.start();
|
||||
try {
|
||||
assertTrue(process.waitFor(2, TimeUnit.MINUTES), "early load did not finish");
|
||||
} finally {
|
||||
process.destroyForcibly();
|
||||
}
|
||||
|
||||
final List<String> output = Files.readAllLines(outputFile);
|
||||
final String programOutput = output.stream().filter(line -> !line.contains(CLASS_INIT_LOG)).collect(Collectors.joining("\n"));
|
||||
assertEquals(0, process.exitValue(), () -> "early load failed:\n" + programOutput);
|
||||
assertTrue(output.contains(DONE), () -> "early load did not complete:\n" + programOutput);
|
||||
|
||||
String lastConfigClass = "<none>";
|
||||
for (final String line : output) {
|
||||
final String initialized = initializedClass(line);
|
||||
if (initialized == null) {
|
||||
continue;
|
||||
}
|
||||
if (initialized.startsWith("io/papermc/paper/configuration/")) {
|
||||
lastConfigClass = initialized;
|
||||
} else if (BOOTSTRAP_CLASSES.contains(initialized)) {
|
||||
fail("The early global config load initialized " + initialized + ", which requires the game to be bootstrapped. "
|
||||
+ "Check the @LoadEarly fields and the field initializers of the sections containing them. "
|
||||
+ "Last initialized config class: " + lastConfigClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable String initializedClass(final String line) {
|
||||
if (!line.contains(CLASS_INIT_LOG)) {
|
||||
return null;
|
||||
}
|
||||
final int start = line.indexOf("Initializing '");
|
||||
if (start == -1) {
|
||||
return null;
|
||||
}
|
||||
final int nameStart = start + "Initializing '".length();
|
||||
return line.substring(nameStart, line.indexOf('\'', nameStart));
|
||||
}
|
||||
|
||||
// Runs in the separate JVM
|
||||
public static final class EarlyLoad {
|
||||
|
||||
public static void main(final String[] args) throws Exception {
|
||||
final Path dir = Path.of(args[0]);
|
||||
|
||||
// the defaults create every section containing early fields
|
||||
final GlobalConfiguration defaults = PaperConfigurationsInitializer.loadEarlyGlobalConfig(dir.resolve("missing.yml"));
|
||||
|
||||
// write all early fields out, so loading them back goes through every early field's serializer
|
||||
final Path configDir = dir.resolve("config");
|
||||
Files.createDirectories(configDir);
|
||||
final YamlConfigurationLoader loader = ConfigurationLoaders.naturallySorted()
|
||||
.defaultOptions(options -> PaperConfigurations.earlyGlobalOptions(options, defaults))
|
||||
.path(configDir.resolve(PaperConfigurations.GLOBAL_CONFIG_FILE_NAME))
|
||||
.build();
|
||||
final ConfigurationNode node = loader.createNode();
|
||||
node.set(GlobalConfiguration.class, defaults);
|
||||
loader.save(node);
|
||||
|
||||
// old versions go through every versioned transformation that doesn't require the server
|
||||
final Path oldConfig = dir.resolve("old.yml");
|
||||
Files.writeString(oldConfig, """
|
||||
%s: 28
|
||||
packet-limiter:
|
||||
overrides:
|
||||
ServerboundChatPacket:
|
||||
max-packet-rate: 1.0
|
||||
""".formatted(Configuration.VERSION_FIELD));
|
||||
PaperConfigurationsInitializer.loadEarlyGlobalConfig(oldConfig);
|
||||
|
||||
// legacy files that weren't converted yet are read too
|
||||
final Path bukkitConfig = dir.resolve("bukkit.yml");
|
||||
final Path spigotConfig = dir.resolve("spigot.yml");
|
||||
Files.writeString(bukkitConfig, "settings:\n world-container: worlds\n update-folder: update\n");
|
||||
Files.writeString(spigotConfig, "advancements:\n disabled:\n - minecraft:story/root\n");
|
||||
|
||||
final OptionParser parser = new OptionParser();
|
||||
parser.accepts("paper-settings-directory").withRequiredArg().ofType(File.class);
|
||||
parser.accepts("bukkit-settings").withRequiredArg().ofType(File.class);
|
||||
parser.accepts("spigot-settings").withRequiredArg().ofType(File.class);
|
||||
PaperConfigurationsInitializer.load(parser.parse(
|
||||
"--paper-settings-directory=" + configDir,
|
||||
"--bukkit-settings=" + bukkitConfig,
|
||||
"--spigot-settings=" + spigotConfig
|
||||
));
|
||||
PaperConfigurationsInitializer.earlyGlobalConfig();
|
||||
|
||||
System.out.println(DONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package io.papermc.paper.configuration;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Set;
|
||||
import org.bukkit.support.environment.Normal;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
@Normal
|
||||
public class EarlyGlobalConfigTest {
|
||||
|
||||
@TempDir
|
||||
Path dir;
|
||||
|
||||
@Test
|
||||
public void onlyEarlyFieldsAreLoaded() throws Exception {
|
||||
final Path file = this.dir.resolve(PaperConfigurations.GLOBAL_CONFIG_FILE_NAME);
|
||||
Files.writeString(file, """
|
||||
%s: %d
|
||||
misc:
|
||||
world-container: worlds
|
||||
netty-threads: 13
|
||||
advancements:
|
||||
disabled:
|
||||
- minecraft:story/root
|
||||
proxies:
|
||||
velocity:
|
||||
enabled: true
|
||||
""".formatted(Configuration.VERSION_FIELD, GlobalConfiguration.CURRENT_VERSION));
|
||||
|
||||
final GlobalConfiguration config = PaperConfigurationsInitializer.loadEarlyGlobalConfig(file);
|
||||
assertEquals("worlds", config.misc.worldContainer);
|
||||
assertEquals("update", config.misc.updateFolder);
|
||||
assertEquals(Set.of("minecraft:story/root"), config.advancements.disabled);
|
||||
|
||||
assertEquals(4, config.misc.nettyThreads, "non-early field next to early ones must keep its default");
|
||||
assertNotEquals("13", System.getProperty("io.netty.eventLoopThreads"), "post-processors must not run");
|
||||
assertNull(config.proxies, "sections without early fields must not be created");
|
||||
assertNull(config.chunkSystem, "sections without early fields must not be created");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void oldVersionLoadsWithoutServer() throws Exception {
|
||||
final Path file = this.dir.resolve(PaperConfigurations.GLOBAL_CONFIG_FILE_NAME);
|
||||
Files.writeString(file, """
|
||||
%s: 28
|
||||
logging:
|
||||
log-player-ip-addresses: false
|
||||
misc:
|
||||
world-container: worlds
|
||||
""".formatted(Configuration.VERSION_FIELD));
|
||||
|
||||
final GlobalConfiguration config = PaperConfigurationsInitializer.loadEarlyGlobalConfig(file);
|
||||
assertEquals("worlds", config.misc.worldContainer);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultsWithoutFile() throws Exception {
|
||||
final GlobalConfiguration config = PaperConfigurationsInitializer.loadEarlyGlobalConfig(this.dir.resolve("missing.yml"));
|
||||
assertEquals(".", config.misc.worldContainer);
|
||||
assertEquals("update", config.misc.updateFolder);
|
||||
assertEquals(Set.of("minecraft:story/disabled"), config.advancements.disabled);
|
||||
assertNull(config.proxies);
|
||||
}
|
||||
}
|
||||
+1
-4
@@ -1,7 +1,6 @@
|
||||
package io.papermc.paper.configuration;
|
||||
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.serialize.SerializationException;
|
||||
|
||||
public final class GlobalConfigTestingBase {
|
||||
@@ -9,10 +8,8 @@ public final class GlobalConfigTestingBase {
|
||||
public static void setupGlobalConfigForTest(RegistryAccess registryAccess) {
|
||||
//noinspection ConstantConditions
|
||||
if (GlobalConfiguration.get() == null) {
|
||||
ConfigurationNode node = PaperConfigurations.createForTesting(registryAccess);
|
||||
try {
|
||||
GlobalConfiguration globalConfiguration = node.require(GlobalConfiguration.class);
|
||||
GlobalConfiguration.set(globalConfiguration);
|
||||
GlobalConfiguration.set(PaperConfigurations.createGlobalForTesting(registryAccess));
|
||||
} catch (SerializationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
package io.papermc.paper.configuration;
|
||||
|
||||
import io.papermc.paper.configuration.Configurations.ContextMap;
|
||||
import io.papermc.paper.configuration.transformation.ServerPropertiesAccess;
|
||||
import java.util.Properties;
|
||||
import java.util.function.Consumer;
|
||||
import net.minecraft.server.dedicated.DedicatedServerProperties;
|
||||
import org.bukkit.support.environment.Normal;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.spongepowered.configurate.BasicConfigurationNode;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.serialize.SerializationException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@Normal
|
||||
public class GlobalTransformationsTest {
|
||||
|
||||
private static final class FakeServerProperties implements ServerPropertiesAccess {
|
||||
private DedicatedServerProperties current;
|
||||
|
||||
private FakeServerProperties(final Properties properties) {
|
||||
this.current = new DedicatedServerProperties(properties, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DedicatedServerProperties current() {
|
||||
return this.current;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(final Consumer<Properties> editor) {
|
||||
final Properties copy = new Properties();
|
||||
copy.putAll(this.current.properties);
|
||||
editor.accept(copy);
|
||||
this.current = new DedicatedServerProperties(copy, null);
|
||||
}
|
||||
}
|
||||
|
||||
private static ConfigurationNode node(final int version) throws SerializationException {
|
||||
final ConfigurationNode node = BasicConfigurationNode.root();
|
||||
node.node(Configuration.VERSION_FIELD).set(version);
|
||||
return node;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void missingContextFailsFullLoad() throws SerializationException {
|
||||
final ConfigurationNode node = node(30);
|
||||
final IllegalStateException exception = assertThrows(IllegalStateException.class,
|
||||
() -> PaperConfigurations.applyGlobalTransformations(ContextMap.builder().build(), node));
|
||||
assertTrue(exception.getMessage().contains("server properties"), exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void earlyLoadSkipsTransformationsMissingContext() throws Exception {
|
||||
final ConfigurationNode node = node(30);
|
||||
node.node("player-auto-save", "rate").set(5);
|
||||
PaperConfigurations.applyEarlyGlobalTransformations(node);
|
||||
|
||||
assertEquals(5, node.node("players", "auto-save", "rate").getInt(), "transformations without requirements still run early");
|
||||
assertTrue(node.node("misc", "enable-nether").virtual(), "V31 requires server properties and must be skipped");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serverPropertiesAreMigrated() throws Exception {
|
||||
final Properties properties = new Properties();
|
||||
properties.setProperty("allow-nether", "false");
|
||||
properties.setProperty("log-ips", "true");
|
||||
final FakeServerProperties serverProperties = new FakeServerProperties(properties);
|
||||
final ContextMap context = ContextMap.builder().put(Configurations.SERVER_PROPERTIES, serverProperties).build();
|
||||
|
||||
final ConfigurationNode node = node(28);
|
||||
node.node("logging", "log-player-ip-addresses").set(false);
|
||||
PaperConfigurations.applyGlobalTransformations(context, node);
|
||||
|
||||
assertFalse(serverProperties.current().logIPs, "V29 moves the option to server.properties");
|
||||
assertTrue(node.node("logging", "log-player-ip-addresses").virtual());
|
||||
assertFalse(node.node("misc", "enable-nether").getBoolean(true), "V31 moves the option from server.properties");
|
||||
assertFalse(serverProperties.current().properties.containsKey("allow-nether"));
|
||||
assertEquals(GlobalConfiguration.CURRENT_VERSION, node.node(Configuration.VERSION_FIELD).getInt());
|
||||
}
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
package io.papermc.paper.configuration.constraint;
|
||||
|
||||
import io.papermc.paper.configuration.type.number.DoubleOr;
|
||||
import io.papermc.paper.configuration.type.number.IntOr;
|
||||
import io.papermc.paper.configuration.type.number.LongOr;
|
||||
import org.bukkit.support.environment.Normal;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.spongepowered.configurate.BasicConfigurationNode;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.ConfigurationOptions;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.ObjectMapper;
|
||||
import org.spongepowered.configurate.serialize.SerializationException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
@Normal
|
||||
public class ConstraintsTest {
|
||||
|
||||
@ConfigSerializable
|
||||
static class Ranged {
|
||||
@Constraints.Min(1)
|
||||
@Constraints.Max(10)
|
||||
int primitiveInt = 5;
|
||||
@Constraints.Min(0.5)
|
||||
double primitiveDouble = 1;
|
||||
@Constraints.Min(1)
|
||||
IntOr.Default intOr = IntOr.Default.USE_DEFAULT;
|
||||
@Constraints.Max(10)
|
||||
LongOr.Default longOr = LongOr.Default.USE_DEFAULT;
|
||||
@Constraints.Min(0.5)
|
||||
DoubleOr.Default doubleOr = DoubleOr.Default.USE_DEFAULT;
|
||||
}
|
||||
|
||||
@ConfigSerializable
|
||||
static class Unsupported {
|
||||
@Constraints.Min(1)
|
||||
String value = "a";
|
||||
}
|
||||
|
||||
private static ConfigurationNode node() {
|
||||
final ObjectMapper.Factory factory = ObjectMapper.factoryBuilder()
|
||||
.addConstraint(Constraints.Min.class, new Constraints.Min.Factory())
|
||||
.addConstraint(Constraints.Max.class, new Constraints.Max.Factory())
|
||||
.build();
|
||||
return BasicConfigurationNode.root(ConfigurationOptions.defaults().serializers(builder -> builder
|
||||
.registerAnnotatedObjects(factory)
|
||||
.register(IntOr.Default.SERIALIZER)
|
||||
.register(LongOr.Default.SERIALIZER)
|
||||
.register(DoubleOr.Default.SERIALIZER)));
|
||||
}
|
||||
|
||||
private static void assertRejected(final String key, final Object value) throws SerializationException {
|
||||
final ConfigurationNode node = node();
|
||||
node.node(key).set(value);
|
||||
assertThrows(SerializationException.class, () -> node.get(Ranged.class), key + "=" + value + " should be rejected");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void inRangeValuesAreAccepted() throws SerializationException {
|
||||
final ConfigurationNode node = node();
|
||||
node.node("primitive-int").set(10);
|
||||
node.node("primitive-double").set(0.5);
|
||||
node.node("int-or").set(1);
|
||||
node.node("long-or").set(10L);
|
||||
node.node("double-or").set(0.5);
|
||||
assertDoesNotThrow(() -> node.get(Ranged.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultValuesAreNotRangeChecked() {
|
||||
final ConfigurationNode node = node();
|
||||
assertDoesNotThrow(() -> node.get(Ranged.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void outOfRangeValuesAreRejected() throws SerializationException {
|
||||
assertRejected("primitive-int", 0);
|
||||
assertRejected("primitive-int", 11);
|
||||
assertRejected("primitive-double", 0.4);
|
||||
assertRejected("int-or", 0);
|
||||
assertRejected("long-or", 11L);
|
||||
assertRejected("double-or", 0.4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unsupportedTypesFailWhenMapperIsCreated() {
|
||||
final ConfigurationNode node = node();
|
||||
assertThrows(Exception.class, () -> node.get(Unsupported.class));
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package io.papermc.paper.configuration.mapping;
|
||||
|
||||
import io.papermc.paper.configuration.ConfigurationPart;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.List;
|
||||
import org.bukkit.support.environment.Normal;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.spongepowered.configurate.BasicConfigurationNode;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.ConfigurationOptions;
|
||||
import org.spongepowered.configurate.objectmapping.ObjectMapper;
|
||||
import org.spongepowered.configurate.serialize.SerializationException;
|
||||
|
||||
import static io.leangen.geantyref.GenericTypeReflector.erase;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
@Normal
|
||||
public class InnerClassInstanceFactoryTest {
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
@interface Rejected {
|
||||
}
|
||||
|
||||
private static final Definition<Rejected, String, FieldProcessor.Factory<Rejected, String>> REJECTED = new Definition<>(
|
||||
Rejected.class,
|
||||
String.class,
|
||||
(data, annotatedType) -> (target, deserializedValue, valueInField) -> {
|
||||
throw new SerializationException(target.getType(), "rejected " + deserializedValue);
|
||||
}
|
||||
);
|
||||
|
||||
public static class Part extends ConfigurationPart {
|
||||
@Rejected
|
||||
public String first = "a";
|
||||
@Rejected
|
||||
public String second = "b";
|
||||
public String plain = "c";
|
||||
}
|
||||
|
||||
@Test
|
||||
public void processorFailuresAreThrown() throws Exception {
|
||||
final List<Definition<?, ?, ? extends FieldProcessor.Factory<?, ?>>> processors = List.of(REJECTED);
|
||||
final ObjectMapper.Factory factory = ObjectMapper.factoryBuilder()
|
||||
.addDiscoverer(InnerClassFieldDiscoverer.globalConfig(processors))
|
||||
.build();
|
||||
final ConfigurationNode node = BasicConfigurationNode.root(ConfigurationOptions.defaults()
|
||||
.serializers(builder -> builder.register(type -> ConfigurationPart.class.isAssignableFrom(erase(type)), factory.asTypeSerializer())));
|
||||
node.node("first").raw("x");
|
||||
node.node("second").raw("y");
|
||||
|
||||
final SerializationException exception = assertThrows(SerializationException.class, () -> node.get(Part.class));
|
||||
assertEquals(1, exception.getSuppressed().length, "every failing field must be reported");
|
||||
}
|
||||
}
|
||||
+23
-34
@@ -1,7 +1,7 @@
|
||||
package io.papermc.paper.world.structure;
|
||||
|
||||
import io.papermc.paper.configuration.PaperConfigurations;
|
||||
import java.io.File;
|
||||
import io.papermc.paper.configuration.WorldConfiguration;
|
||||
import java.lang.reflect.Field;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
@@ -11,13 +11,9 @@ import net.minecraft.world.level.levelgen.structure.BuiltinStructureSets;
|
||||
import net.minecraft.world.level.levelgen.structure.StructureSet;
|
||||
import net.minecraft.world.level.levelgen.structure.placement.AbstractSpreadingStructurePlacement;
|
||||
import net.minecraft.world.level.levelgen.structure.placement.StructurePlacement;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.support.RegistryHelper;
|
||||
import org.bukkit.support.environment.AllFeatures;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.spigotmc.SpigotConfig;
|
||||
import org.spigotmc.SpigotWorldConfig;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
@@ -26,14 +22,7 @@ public class StructureSeedConfigTest {
|
||||
|
||||
@Test
|
||||
public void checkStructureSeedDefaults() throws ReflectiveOperationException {
|
||||
SpigotConfig.config = new YamlConfiguration() {
|
||||
@Override
|
||||
public void save(final @NotNull File file) {
|
||||
// no-op
|
||||
}
|
||||
};
|
||||
final SpigotWorldConfig config = PaperConfigurations.SPIGOT_WORLD_DEFAULTS.get();
|
||||
|
||||
final WorldConfiguration.Seeds seeds = PaperConfigurations.createWorldDefaultsForTesting().new Seeds();
|
||||
|
||||
final Registry<StructureSet> structureSets = RegistryHelper.registryAccess().lookupOrThrow(Registries.STRUCTURE_SET);
|
||||
for (final ResourceKey<StructureSet> setKey : structureSets.registryKeySet()) {
|
||||
@@ -48,27 +37,27 @@ public class StructureSeedConfigTest {
|
||||
continue;
|
||||
}
|
||||
int salt = switch (setKey.identifier().getPath()) {
|
||||
case "villages" -> config.villageSeed;
|
||||
case "desert_pyramids" -> config.desertSeed;
|
||||
case "igloos" -> config.iglooSeed;
|
||||
case "jungle_temples" -> config.jungleSeed;
|
||||
case "swamp_huts" -> config.swampSeed;
|
||||
case "pillager_outposts" -> config.outpostSeed;
|
||||
case "ocean_monuments" -> config.monumentSeed;
|
||||
case "woodland_mansions" -> config.mansionSeed;
|
||||
case "buried_treasures" -> config.buriedTreasureSeed;
|
||||
case "mineshafts" -> config.mineshaftSeed == null ? 0 : config.mineshaftSeed; // mineshaft seed is set differently
|
||||
case "ruined_portals" -> config.portalSeed;
|
||||
case "shipwrecks" -> config.shipwreckSeed;
|
||||
case "ocean_ruins" -> config.oceanSeed;
|
||||
case "nether_complexes" -> config.netherSeed;
|
||||
case "nether_fossils" -> config.fossilSeed;
|
||||
case "end_cities" -> config.endCitySeed;
|
||||
case "ancient_cities" -> config.ancientCitySeed;
|
||||
case "trail_ruins" -> config.trailRuinsSeed;
|
||||
case "trial_chambers" -> config.trialChambersSeed;
|
||||
case "abandoned_camp" -> config.abandonedCampSeed;
|
||||
default -> throw new AssertionError("Missing structure set seed in SpigotWorldConfig for " + setKey);
|
||||
case "villages" -> seeds.village;
|
||||
case "desert_pyramids" -> seeds.desert;
|
||||
case "igloos" -> seeds.igloo;
|
||||
case "jungle_temples" -> seeds.jungle;
|
||||
case "swamp_huts" -> seeds.swamp;
|
||||
case "pillager_outposts" -> seeds.outpost;
|
||||
case "ocean_monuments" -> seeds.monument;
|
||||
case "woodland_mansions" -> seeds.mansion;
|
||||
case "buried_treasures" -> seeds.buriedTreasure;
|
||||
case "mineshafts" -> seeds.mineshaft.or(0); // mineshaft seed is set differently
|
||||
case "ruined_portals" -> seeds.portal;
|
||||
case "shipwrecks" -> seeds.shipwreck;
|
||||
case "ocean_ruins" -> seeds.ocean;
|
||||
case "nether_complexes" -> seeds.nether;
|
||||
case "nether_fossils" -> seeds.fossil;
|
||||
case "end_cities" -> seeds.endCity;
|
||||
case "ancient_cities" -> seeds.ancientCity;
|
||||
case "trail_ruins" -> seeds.trailRuins;
|
||||
case "trial_chambers" -> seeds.trialChambers;
|
||||
case "abandoned_camp" -> seeds.abandonedCamp;
|
||||
default -> throw new AssertionError("Missing structure set seed in WorldConfiguration.Seeds for " + setKey);
|
||||
};
|
||||
if (setKey == BuiltinStructureSets.BURIED_TREASURES) {
|
||||
final Field field = AbstractSpreadingStructurePlacement.class.getDeclaredField("HIGHLY_ARBITRARY_RANDOM_SALT");
|
||||
|
||||
+1
-1
@@ -1,5 +1,6 @@
|
||||
package org.bukkit.craftbukkit.inventory;
|
||||
|
||||
import io.papermc.paper.configuration.yaml.YamlConfigurationLoader;
|
||||
import io.papermc.paper.datacomponent.DataComponentTypes;
|
||||
import io.papermc.paper.datacomponent.item.ItemEnchantments;
|
||||
import io.papermc.paper.datacomponent.item.Tool;
|
||||
@@ -22,7 +23,6 @@ import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.spongepowered.configurate.ConfigurateException;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
|
||||
Reference in new issue
Block a user