mirror of
https://github.com/PaperMC/Paper.git
synced 2024-11-30 14:33:56 +01:00
4400 lines
206 KiB
Diff
4400 lines
206 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Jake Potrebic <jake.m.potrebic@gmail.com>
|
|
Date: Wed, 8 Jun 2022 22:20:16 -0700
|
|
Subject: [PATCH] Paper config files
|
|
|
|
|
|
diff --git a/build.gradle.kts b/build.gradle.kts
|
|
index 5d8a84341ab5be52b5c37737e3f82590f06f6073..cfdb20447e6ad3efcdee8889712f77931773beaf 100644
|
|
--- a/build.gradle.kts
|
|
+++ b/build.gradle.kts
|
|
@@ -12,6 +12,7 @@ dependencies {
|
|
implementation("org.apache.logging.log4j:log4j-iostreams:2.17.1") // Paper
|
|
implementation("org.ow2.asm:asm:9.3")
|
|
implementation("org.ow2.asm:asm-commons:9.3") // Paper - ASM event executor generation
|
|
+ implementation("org.spongepowered:configurate-yaml:4.1.2") // Paper - config files
|
|
implementation("commons-lang:commons-lang:2.6")
|
|
runtimeOnly("org.xerial:sqlite-jdbc:3.36.0.3")
|
|
runtimeOnly("mysql:mysql-connector-java:8.0.29")
|
|
diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..ef41cf3a7d1e6f2bfe81e0fb865d2f969bbc77c1
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
|
|
@@ -0,0 +1,8 @@
|
|
+package com.destroystokyo.paper;
|
|
+
|
|
+/**
|
|
+ * @deprecated kept as a means to identify Paper in older plugins/PaperLib
|
|
+ */
|
|
+@Deprecated(forRemoval = true)
|
|
+public class PaperConfig {
|
|
+}
|
|
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..c91f109b4cf64dc1b4ef09f38e1cb8bf5cb2be13
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
|
|
@@ -0,0 +1,8 @@
|
|
+package com.destroystokyo.paper;
|
|
+
|
|
+/**
|
|
+ * @deprecated kept as a means to identify Paper in older plugins/PaperLib
|
|
+ */
|
|
+@Deprecated(forRemoval = true)
|
|
+public class PaperWorldConfig {
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/Configuration.java b/src/main/java/io/papermc/paper/configuration/Configuration.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..817fd26cc3591f9cae0f61f4036dde43c4ed60e8
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/configuration/Configuration.java
|
|
@@ -0,0 +1,13 @@
|
|
+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;
|
|
+
|
|
+ private Configuration() {
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/ConfigurationLoaders.java b/src/main/java/io/papermc/paper/configuration/ConfigurationLoaders.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..227039a6c69c4c99bbd9c674b3aab0ef5e2c1374
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/configuration/ConfigurationLoaders.java
|
|
@@ -0,0 +1,27 @@
|
|
+package io.papermc.paper.configuration;
|
|
+
|
|
+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() {
|
|
+ }
|
|
+
|
|
+ public static YamlConfigurationLoader.Builder naturallySorted() {
|
|
+ return YamlConfigurationLoader.builder()
|
|
+ .indent(2)
|
|
+ .nodeStyle(NodeStyle.BLOCK)
|
|
+ .headerMode(HeaderMode.PRESET)
|
|
+ .defaultOptions(options -> options.mapFactory(MapFactories.sortedNatural()));
|
|
+ }
|
|
+
|
|
+ public static YamlConfigurationLoader naturallySortedWithoutHeader(final Path path) {
|
|
+ return naturallySorted()
|
|
+ .headerMode(HeaderMode.NONE)
|
|
+ .path(path)
|
|
+ .build();
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/ConfigurationPart.java b/src/main/java/io/papermc/paper/configuration/ConfigurationPart.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..7a4a7a654fe2516ed894a68f2657344df9d70f4c
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/configuration/ConfigurationPart.java
|
|
@@ -0,0 +1,10 @@
|
|
+package io.papermc.paper.configuration;
|
|
+
|
|
+abstract class ConfigurationPart {
|
|
+
|
|
+ public static abstract class Post extends ConfigurationPart {
|
|
+
|
|
+ public abstract void postProcess();
|
|
+ }
|
|
+
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/Configurations.java b/src/main/java/io/papermc/paper/configuration/Configurations.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..31325994ab441c59a4c0bd9f3f9db3d9440375d0
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/configuration/Configurations.java
|
|
@@ -0,0 +1,296 @@
|
|
+package io.papermc.paper.configuration;
|
|
+
|
|
+import io.leangen.geantyref.TypeToken;
|
|
+import io.papermc.paper.configuration.constraint.Constraint;
|
|
+import io.papermc.paper.configuration.constraint.Constraints;
|
|
+import net.minecraft.resources.ResourceLocation;
|
|
+import net.minecraft.server.level.ServerLevel;
|
|
+import org.checkerframework.checker.nullness.qual.Nullable;
|
|
+import org.jetbrains.annotations.MustBeInvokedByOverriders;
|
|
+import org.spongepowered.configurate.CommentedConfigurationNode;
|
|
+import org.spongepowered.configurate.ConfigurateException;
|
|
+import org.spongepowered.configurate.ConfigurationNode;
|
|
+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;
|
|
+
|
|
+import java.io.IOException;
|
|
+import java.lang.reflect.Type;
|
|
+import java.nio.file.Files;
|
|
+import java.nio.file.Path;
|
|
+import java.util.HashMap;
|
|
+import java.util.Map;
|
|
+import java.util.NoSuchElementException;
|
|
+import java.util.Objects;
|
|
+import java.util.function.UnaryOperator;
|
|
+
|
|
+public abstract class Configurations<G, W> {
|
|
+
|
|
+ public static final String WORLD_DEFAULTS = "__world_defaults__";
|
|
+ public static final ResourceLocation WORLD_DEFAULTS_KEY = new ResourceLocation("configurations", WORLD_DEFAULTS);
|
|
+ protected final Path globalFolder;
|
|
+ protected final Class<G> globalConfigClass;
|
|
+ protected final Class<W> worldConfigClass;
|
|
+ protected final String globalConfigFileName;
|
|
+ protected final String defaultWorldConfigFileName;
|
|
+ protected final String worldConfigFileName;
|
|
+
|
|
+ public Configurations(
|
|
+ final Path globalFolder,
|
|
+ final Class<G> globalConfigType,
|
|
+ final Class<W> worldConfigClass,
|
|
+ final String globalConfigFileName,
|
|
+ final String defaultWorldConfigFileName,
|
|
+ final String worldConfigFileName
|
|
+ ) {
|
|
+ this.globalFolder = globalFolder;
|
|
+ this.globalConfigClass = globalConfigType;
|
|
+ this.worldConfigClass = worldConfigClass;
|
|
+ this.globalConfigFileName = globalConfigFileName;
|
|
+ this.defaultWorldConfigFileName = defaultWorldConfigFileName;
|
|
+ this.worldConfigFileName = worldConfigFileName;
|
|
+ }
|
|
+
|
|
+ protected ObjectMapper.Factory.Builder createObjectMapper() {
|
|
+ return ObjectMapper.factoryBuilder()
|
|
+ .addConstraint(Constraint.class, new Constraint.Factory())
|
|
+ .addConstraint(Constraints.Min.class, Number.class, new Constraints.Min.Factory());
|
|
+ }
|
|
+
|
|
+ protected YamlConfigurationLoader.Builder createLoaderBuilder() {
|
|
+ return ConfigurationLoaders.naturallySorted();
|
|
+ }
|
|
+
|
|
+ protected abstract boolean isConfigType(final Type type);
|
|
+
|
|
+ protected ObjectMapper.Factory.Builder createGlobalObjectMapperFactoryBuilder() {
|
|
+ return this.createObjectMapper();
|
|
+ }
|
|
+
|
|
+ @MustBeInvokedByOverriders
|
|
+ protected YamlConfigurationLoader.Builder createGlobalLoaderBuilder() {
|
|
+ return this.createLoaderBuilder();
|
|
+ }
|
|
+
|
|
+ static <T> CheckedFunction<ConfigurationNode, T, SerializationException> creator(Class<T> type, boolean refreshNode) {
|
|
+ return node -> {
|
|
+ T instance = node.require(type);
|
|
+ if (refreshNode) {
|
|
+ node.set(type, instance);
|
|
+ }
|
|
+ return instance;
|
|
+ };
|
|
+ }
|
|
+
|
|
+ static <T> CheckedFunction<ConfigurationNode, T, SerializationException> reloader(Class<T> type, T instance) {
|
|
+ return node -> {
|
|
+ ObjectMapper.Factory factory = (ObjectMapper.Factory) Objects.requireNonNull(node.options().serializers().get(type));
|
|
+ ObjectMapper.Mutable<T> mutable = (ObjectMapper.Mutable<T>) factory.get(type);
|
|
+ mutable.load(instance, node);
|
|
+ return instance;
|
|
+ };
|
|
+ }
|
|
+
|
|
+ public G initializeGlobalConfiguration() throws ConfigurateException {
|
|
+ return this.initializeGlobalConfiguration(creator(this.globalConfigClass, true));
|
|
+ }
|
|
+
|
|
+ protected G initializeGlobalConfiguration(final CheckedFunction<ConfigurationNode, G, SerializationException> creator) throws ConfigurateException {
|
|
+ final Path configFile = this.globalFolder.resolve(this.globalConfigFileName);
|
|
+ final YamlConfigurationLoader loader = this.createGlobalLoaderBuilder()
|
|
+ .defaultOptions(this.applyObjectMapperFactory(this.createGlobalObjectMapperFactoryBuilder().build()))
|
|
+ .path(configFile)
|
|
+ .build();
|
|
+ final ConfigurationNode node;
|
|
+ if (Files.exists(configFile)) {
|
|
+ node = loader.load();
|
|
+ } else {
|
|
+ node = CommentedConfigurationNode.root(loader.defaultOptions());
|
|
+ }
|
|
+ this.applyGlobalConfigTransformations(node);
|
|
+ final G instance = creator.apply(node);
|
|
+ loader.save(node);
|
|
+ return instance;
|
|
+ }
|
|
+
|
|
+ protected void applyGlobalConfigTransformations(final ConfigurationNode node) throws ConfigurateException {
|
|
+ }
|
|
+
|
|
+ @MustBeInvokedByOverriders
|
|
+ protected ContextMap.Builder createDefaultContextMap() {
|
|
+ return ContextMap.builder()
|
|
+ .put(WORLD_NAME, WORLD_DEFAULTS)
|
|
+ .put(WORLD_KEY, WORLD_DEFAULTS_KEY);
|
|
+ }
|
|
+
|
|
+ public void initializeWorldDefaultsConfiguration() throws ConfigurateException {
|
|
+ final ContextMap contextMap = this.createDefaultContextMap()
|
|
+ .put(FIRST_DEFAULT)
|
|
+ .build();
|
|
+ final DefaultWorldLoader result = this.createDefaultWorldLoader(false, contextMap);
|
|
+ final YamlConfigurationLoader loader = result.loader();
|
|
+ final ConfigurationNode node = loader.load();
|
|
+ if (result.isNewFile()) { // add version to new files
|
|
+ node.node(Configuration.VERSION_FIELD).raw(WorldConfiguration.CURRENT_VERSION);
|
|
+ }
|
|
+ this.applyWorldConfigTransformations(contextMap, node);
|
|
+ final W instance = node.require(this.worldConfigClass);
|
|
+ node.set(this.worldConfigClass, instance);
|
|
+ loader.save(node);
|
|
+ }
|
|
+
|
|
+ private DefaultWorldLoader createDefaultWorldLoader(final boolean requireFile, final ContextMap contextMap) {
|
|
+ final Path configFile = this.globalFolder.resolve(this.defaultWorldConfigFileName);
|
|
+ boolean willCreate = Files.notExists(configFile);
|
|
+ if (requireFile && willCreate) {
|
|
+ throw new IllegalStateException("World defaults configuration file '" + configFile + "' doesn't exist");
|
|
+ }
|
|
+ return new DefaultWorldLoader(
|
|
+ this.createWorldConfigLoaderBuilder(contextMap)
|
|
+ .defaultOptions(this.applyObjectMapperFactory(this.createWorldObjectMapperFactoryBuilder(contextMap).build()))
|
|
+ .path(configFile)
|
|
+ .build(),
|
|
+ willCreate
|
|
+ );
|
|
+ }
|
|
+
|
|
+ private record DefaultWorldLoader(YamlConfigurationLoader loader, boolean isNewFile) {
|
|
+ }
|
|
+
|
|
+ protected ObjectMapper.Factory.Builder createWorldObjectMapperFactoryBuilder(final ContextMap contextMap) {
|
|
+ return this.createObjectMapper();
|
|
+ }
|
|
+
|
|
+ @MustBeInvokedByOverriders
|
|
+ protected YamlConfigurationLoader.Builder createWorldConfigLoaderBuilder(final ContextMap contextMap) {
|
|
+ return this.createLoaderBuilder();
|
|
+ }
|
|
+
|
|
+ // Make sure to run version transforms on the default world config first via #setupWorldDefaultsConfig
|
|
+ public W createWorldConfig(final ContextMap contextMap) throws IOException {
|
|
+ return this.createWorldConfig(contextMap, creator(this.worldConfigClass, false));
|
|
+ }
|
|
+
|
|
+ protected W createWorldConfig(final ContextMap contextMap, final CheckedFunction<ConfigurationNode, W, SerializationException> creator) throws IOException {
|
|
+ final YamlConfigurationLoader defaultsLoader = this.createDefaultWorldLoader(true, this.createDefaultContextMap().build()).loader();
|
|
+ final ConfigurationNode defaultsNode = defaultsLoader.load();
|
|
+
|
|
+ boolean newFile = false;
|
|
+ final Path dir = contextMap.require(WORLD_DIRECTORY);
|
|
+ final Path worldConfigFile = dir.resolve(this.worldConfigFileName);
|
|
+ if (Files.notExists(worldConfigFile)) {
|
|
+ PaperConfigurations.createDirectoriesSymlinkAware(dir);
|
|
+ Files.createFile(worldConfigFile); // create empty file as template
|
|
+ newFile = true;
|
|
+ }
|
|
+
|
|
+ final YamlConfigurationLoader worldLoader = this.createWorldConfigLoaderBuilder(contextMap)
|
|
+ .defaultOptions(this.applyObjectMapperFactory(this.createWorldObjectMapperFactoryBuilder(contextMap).build()))
|
|
+ .path(worldConfigFile)
|
|
+ .build();
|
|
+ final ConfigurationNode worldNode = worldLoader.load();
|
|
+ if (newFile) { // set the version field if new file
|
|
+ worldNode.node(Configuration.VERSION_FIELD).set(WorldConfiguration.CURRENT_VERSION);
|
|
+ }
|
|
+ this.applyWorldConfigTransformations(contextMap, worldNode);
|
|
+ this.applyDefaultsAwareWorldConfigTransformations(contextMap, worldNode, defaultsNode);
|
|
+ worldLoader.save(worldNode); // save before loading node NOTE: don't save the backing node after loading it, or you'll fill up the world-specific config
|
|
+ worldNode.mergeFrom(defaultsNode);
|
|
+ return creator.apply(worldNode);
|
|
+ }
|
|
+
|
|
+ protected void applyWorldConfigTransformations(final ContextMap contextMap, final ConfigurationNode node) throws ConfigurateException {
|
|
+ }
|
|
+
|
|
+ protected void applyDefaultsAwareWorldConfigTransformations(final ContextMap contextMap, final ConfigurationNode worldNode, final ConfigurationNode defaultsNode) throws ConfigurateException {
|
|
+ }
|
|
+
|
|
+ private UnaryOperator<ConfigurationOptions> applyObjectMapperFactory(final ObjectMapper.Factory factory) {
|
|
+ return options -> options.serializers(builder -> builder
|
|
+ .register(this::isConfigType, factory.asTypeSerializer())
|
|
+ .registerAnnotatedObjects(factory));
|
|
+ }
|
|
+
|
|
+ public Path getWorldConfigFile(ServerLevel level) {
|
|
+ return level.convertable.levelDirectory.path().resolve(this.worldConfigFileName);
|
|
+ }
|
|
+
|
|
+ public static class ContextMap {
|
|
+ private static final Object VOID = new Object();
|
|
+
|
|
+ public static Builder builder() {
|
|
+ return new Builder();
|
|
+ }
|
|
+
|
|
+ private final Map<ContextKey<?>, Object> backingMap;
|
|
+
|
|
+ private ContextMap(Map<ContextKey<?>, Object> map) {
|
|
+ this.backingMap = Map.copyOf(map);
|
|
+ }
|
|
+
|
|
+ @SuppressWarnings("unchecked")
|
|
+ public <T> T require(ContextKey<T> key) {
|
|
+ final @Nullable Object value = this.backingMap.get(key);
|
|
+ if (value == null) {
|
|
+ throw new NoSuchElementException("No element found for " + key + " with type " + key.type());
|
|
+ } else if (value == VOID) {
|
|
+ throw new IllegalArgumentException("Cannot get the value of a Void key");
|
|
+ }
|
|
+ return (T) value;
|
|
+ }
|
|
+
|
|
+ @SuppressWarnings("unchecked")
|
|
+ public <T> @Nullable T get(ContextKey<T> key) {
|
|
+ return (T) this.backingMap.get(key);
|
|
+ }
|
|
+
|
|
+ public boolean has(ContextKey<?> key) {
|
|
+ return this.backingMap.containsKey(key);
|
|
+ }
|
|
+
|
|
+ public boolean isDefaultWorldContext() {
|
|
+ return this.require(WORLD_KEY).equals(WORLD_DEFAULTS_KEY);
|
|
+ }
|
|
+
|
|
+ public static class Builder {
|
|
+
|
|
+ private Builder() {
|
|
+ }
|
|
+
|
|
+ private final Map<ContextKey<?>, Object> buildingMap = new HashMap<>();
|
|
+
|
|
+ public <T> Builder put(ContextKey<T> key, T value) {
|
|
+ this.buildingMap.put(key, value);
|
|
+ return this;
|
|
+ }
|
|
+
|
|
+ public Builder put(ContextKey<Void> key) {
|
|
+ this.buildingMap.put(key, VOID);
|
|
+ return this;
|
|
+ }
|
|
+
|
|
+ public ContextMap build() {
|
|
+ return new ContextMap(this.buildingMap);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public static final ContextKey<Path> WORLD_DIRECTORY = new ContextKey<>(Path.class, "world directory");
|
|
+ public static final ContextKey<String> WORLD_NAME = new ContextKey<>(String.class, "world name"); // TODO remove when we deprecate level names
|
|
+ public static final ContextKey<ResourceLocation> WORLD_KEY = new ContextKey<>(ResourceLocation.class, "world key");
|
|
+ public static final ContextKey<Void> FIRST_DEFAULT = new ContextKey<>(Void.class, "first default");
|
|
+
|
|
+ public record ContextKey<T>(TypeToken<T> type, String name) {
|
|
+
|
|
+ public ContextKey(Class<T> type, String name) {
|
|
+ this(TypeToken.get(type), name);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public String toString() {
|
|
+ return "ContextKey{" + this.name + "}";
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..456595e4b7e0c7f50617aa2694b0d2dfc368ab81
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java
|
|
@@ -0,0 +1,265 @@
|
|
+package io.papermc.paper.configuration;
|
|
+
|
|
+import co.aikar.timings.MinecraftTimings;
|
|
+import com.destroystokyo.paper.io.chunk.ChunkTaskManager;
|
|
+import io.papermc.paper.configuration.constraint.Constraint;
|
|
+import io.papermc.paper.configuration.constraint.Constraints;
|
|
+import net.kyori.adventure.text.Component;
|
|
+import net.kyori.adventure.text.format.NamedTextColor;
|
|
+import net.minecraft.network.protocol.Packet;
|
|
+import net.minecraft.network.protocol.game.ServerboundPlaceRecipePacket;
|
|
+import org.checkerframework.checker.nullness.qual.Nullable;
|
|
+import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
|
+import org.spongepowered.configurate.objectmapping.meta.Comment;
|
|
+import org.spongepowered.configurate.objectmapping.meta.Required;
|
|
+import org.spongepowered.configurate.objectmapping.meta.Setting;
|
|
+
|
|
+import java.util.List;
|
|
+import java.util.Map;
|
|
+import java.util.Objects;
|
|
+
|
|
+@SuppressWarnings({"CanBeFinal", "FieldCanBeLocal", "FieldMayBeFinal", "NotNullFieldNotInitialized", "InnerClassMayBeStatic"})
|
|
+public class GlobalConfiguration extends ConfigurationPart {
|
|
+ static final int CURRENT_VERSION = 28;
|
|
+ private static GlobalConfiguration instance;
|
|
+ public static GlobalConfiguration get() {
|
|
+ return instance;
|
|
+ }
|
|
+ static void set(GlobalConfiguration instance) {
|
|
+ GlobalConfiguration.instance = instance;
|
|
+ }
|
|
+
|
|
+ @Setting(Configuration.VERSION_FIELD)
|
|
+ public int version = CURRENT_VERSION;
|
|
+
|
|
+ public Messages messages;
|
|
+
|
|
+ public class Messages extends ConfigurationPart {
|
|
+ public Kick kick;
|
|
+
|
|
+ public class Kick extends ConfigurationPart {
|
|
+ public Component authenticationServersDown = Component.translatable("multiplayer.disconnect.authservers_down");
|
|
+ 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 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 Timings timings;
|
|
+
|
|
+ public class Timings extends ConfigurationPart.Post {
|
|
+ public boolean enabled = true;
|
|
+ public boolean verbose = true;
|
|
+ public String url = "https://timings.aikar.co/";
|
|
+ public boolean serverNamePrivacy = false;
|
|
+ public List<String> hiddenConfigEntries = List.of(
|
|
+ "database",
|
|
+ "proxies.velocity.secret"
|
|
+ );
|
|
+ public int historyInterval = 300;
|
|
+ public int historyLength = 3600;
|
|
+ public String serverName = "Unknown Server";
|
|
+
|
|
+ @Override
|
|
+ public void postProcess() {
|
|
+ MinecraftTimings.processConfig(this);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public Proxies proxies;
|
|
+
|
|
+ public class Proxies extends ConfigurationPart {
|
|
+ public BungeeCord bungeeCord;
|
|
+
|
|
+ public class BungeeCord extends ConfigurationPart {
|
|
+ public boolean onlineMode = true;
|
|
+ }
|
|
+
|
|
+ @Constraint(Constraints.Velocity.class)
|
|
+ public Velocity velocity;
|
|
+
|
|
+ public class Velocity extends ConfigurationPart {
|
|
+ public boolean enabled = false;
|
|
+ public boolean onlineMode = false;
|
|
+ public String secret = "";
|
|
+ }
|
|
+ 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);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public Console console;
|
|
+
|
|
+ public class Console extends ConfigurationPart {
|
|
+ public boolean enableBrigadierHighlighting = true;
|
|
+ public boolean enableBrigadierCompletions = true;
|
|
+ public boolean hasAllPermissions = false;
|
|
+ }
|
|
+
|
|
+ public Watchdog watchdog;
|
|
+
|
|
+ public class Watchdog extends ConfigurationPart {
|
|
+ public int earlyWarningEvery = 5000;
|
|
+ public int earlyWarningDelay = 10000;
|
|
+ }
|
|
+
|
|
+ public SpamLimiter spamLimiter;
|
|
+
|
|
+ public class SpamLimiter extends ConfigurationPart {
|
|
+ public int tabSpamIncrement = 1;
|
|
+ public int tabSpamLimit = 500;
|
|
+ public int recipeSpamIncrement = 1;
|
|
+ public int recipeSpamLimit = 20;
|
|
+ public int incomingPacketThreshold = 300;
|
|
+ }
|
|
+
|
|
+ public ChunkLoading chunkLoading;
|
|
+
|
|
+ public class ChunkLoading extends ConfigurationPart {
|
|
+ public int minLoadRadius = 2;
|
|
+ public int maxConcurrentSends = 2;
|
|
+ public boolean autoconfigSendDistance = true;
|
|
+ public double targetPlayerChunkSendRate = 100.0;
|
|
+ public double globalMaxChunkSendRate = -1.0;
|
|
+ public boolean enableFrustumPriority = false;
|
|
+ public double globalMaxChunkLoadRate = -1.0;
|
|
+ public double playerMaxConcurrentLoads = 20.0;
|
|
+ public double globalMaxConcurrentLoads = 500.0;
|
|
+ public double playerMaxChunkLoadRate = -1.0;
|
|
+ }
|
|
+
|
|
+ public UnsupportedSettings unsupportedSettings;
|
|
+
|
|
+ public class UnsupportedSettings extends ConfigurationPart {
|
|
+ @Comment("This setting controls if players should be able to break bedrock, end portals and other intended to be permanent blocks.")
|
|
+ public boolean allowPermanentBlockBreakExploits = false;
|
|
+ @Comment("This setting controls if player should be able to use TNT duplication, but this also allows duplicating carpet, rails and potentially other items")
|
|
+ public boolean allowPistonDuplication = false;
|
|
+ public boolean performUsernameValidation = true;
|
|
+ @Comment("This setting controls if players should be able to create headless pistons.")
|
|
+ public boolean allowHeadlessPistons = false;
|
|
+ }
|
|
+
|
|
+ public Commands commands;
|
|
+
|
|
+ public class Commands extends ConfigurationPart {
|
|
+ public boolean suggestPlayerNamesWhenNullTabCompletions = true;
|
|
+ public boolean fixTargetSelectorTagCompletion = true;
|
|
+ public boolean timeCommandAffectsAllWorlds = false;
|
|
+ }
|
|
+
|
|
+ public Logging logging;
|
|
+
|
|
+ public class Logging extends ConfigurationPart {
|
|
+ public boolean logPlayerIpAddresses = true;
|
|
+ public boolean deobfuscateStacktraces = true;
|
|
+ public boolean useRgbForNamedTextColors = true;
|
|
+ }
|
|
+
|
|
+ public Scoreboards scoreboards;
|
|
+
|
|
+ public class Scoreboards extends ConfigurationPart {
|
|
+ public boolean trackPluginScoreboards = false;
|
|
+ public boolean saveEmptyScoreboardTeams = false;
|
|
+ }
|
|
+
|
|
+ public AsyncChunks asyncChunks;
|
|
+
|
|
+ public class AsyncChunks extends ConfigurationPart.Post {
|
|
+ public int threads = -1;
|
|
+ public transient boolean asyncChunks = false;
|
|
+
|
|
+ @Override
|
|
+ public void postProcess() {
|
|
+ ChunkTaskManager.processConfiguration(this);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public ItemValidation itemValidation;
|
|
+
|
|
+ public class ItemValidation extends ConfigurationPart {
|
|
+ public int displayName = 8192;
|
|
+ public int loreLine = 8192;
|
|
+ public Book book;
|
|
+
|
|
+ public class Book extends ConfigurationPart {
|
|
+ public int title = 8192;
|
|
+ public int author = 8192;
|
|
+ public int page = 16384;
|
|
+ }
|
|
+
|
|
+ public BookSize bookSize;
|
|
+
|
|
+ public class BookSize extends ConfigurationPart {
|
|
+ public int pageMax = 2560; // TODO this appears to be a duplicate setting with one above
|
|
+ public double totalMultiplier = 0.98D; // TODO this should probably be merged into the above inner class
|
|
+ }
|
|
+ public boolean resolveSelectorsInBooks = false;
|
|
+ }
|
|
+
|
|
+ public PacketLimiter packetLimiter;
|
|
+
|
|
+ public class PacketLimiter extends ConfigurationPart {
|
|
+ public Component kickMessage = Component.translatable("disconnect.exceeded_packet_rate", NamedTextColor.RED);
|
|
+ public PacketLimit allPackets = new PacketLimit(7.0, 500.0, PacketLimit.ViolateAction.KICK);
|
|
+ public Map<Class<? extends Packet<?>>, PacketLimit> overrides = Map.of(ServerboundPlaceRecipePacket.class, new PacketLimit(4.0, 5.0, PacketLimit.ViolateAction.DROP));
|
|
+
|
|
+ @ConfigSerializable
|
|
+ public record PacketLimit(@Required double interval, @Required double maxPacketRate, ViolateAction action) {
|
|
+ public PacketLimit(final double interval, final double maxPacketRate, final @Nullable ViolateAction action) {
|
|
+ this.interval = interval;
|
|
+ this.maxPacketRate = maxPacketRate;
|
|
+ this.action = Objects.requireNonNullElse(action, ViolateAction.KICK);
|
|
+ }
|
|
+
|
|
+ public boolean isEnabled() {
|
|
+ return this.interval > 0.0 && this.maxPacketRate > 0.0;
|
|
+ }
|
|
+
|
|
+ public enum ViolateAction {
|
|
+ KICK,
|
|
+ DROP;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public Collisions collisions;
|
|
+
|
|
+ public class Collisions extends ConfigurationPart {
|
|
+ public boolean enablePlayerCollisions = true;
|
|
+ public boolean sendFullPosForHardCollidingEntities = true;
|
|
+ }
|
|
+
|
|
+ public PlayerAutoSave playerAutoSave;
|
|
+
|
|
+
|
|
+ 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;
|
|
+ }
|
|
+ return this.maxPerTick;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public Misc misc;
|
|
+
|
|
+ public class Misc extends ConfigurationPart {
|
|
+ public int maxJoinsPerTick = 3;
|
|
+ public boolean fixEntityPositionDesync = true;
|
|
+ public boolean loadPermissionsYmlBeforePlugins = true;
|
|
+ @Constraints.Min(4)
|
|
+ public int regionFileCacheSize = 256;
|
|
+ @Comment("See https://luckformula.emc.gs")
|
|
+ public boolean useAlternativeLuckFormula = false;
|
|
+ public boolean lagCompensateBlockBreaking = true;
|
|
+ public boolean useDimensionTypeForCustomSpawners = false;
|
|
+ public boolean strictAdvancementDimensionCheck = false;
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/InnerClassFieldDiscoverer.java b/src/main/java/io/papermc/paper/configuration/InnerClassFieldDiscoverer.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..a0aa1f1a7adf986d500a2135aa42e138aa3c4f08
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/configuration/InnerClassFieldDiscoverer.java
|
|
@@ -0,0 +1,142 @@
|
|
+package io.papermc.paper.configuration;
|
|
+
|
|
+import io.leangen.geantyref.GenericTypeReflector;
|
|
+import org.checkerframework.checker.nullness.qual.Nullable;
|
|
+import org.spongepowered.configurate.objectmapping.FieldDiscoverer;
|
|
+import org.spongepowered.configurate.serialize.SerializationException;
|
|
+import org.spongepowered.configurate.util.CheckedSupplier;
|
|
+
|
|
+import java.lang.reflect.AnnotatedType;
|
|
+import java.lang.reflect.Constructor;
|
|
+import java.lang.reflect.Field;
|
|
+import java.lang.reflect.Modifier;
|
|
+import java.util.Collections;
|
|
+import java.util.HashMap;
|
|
+import java.util.Iterator;
|
|
+import java.util.Map;
|
|
+
|
|
+import static io.leangen.geantyref.GenericTypeReflector.erase;
|
|
+
|
|
+final class InnerClassFieldDiscoverer implements FieldDiscoverer<Map<Field, Object>> {
|
|
+
|
|
+ private final Map<Class<?>, Object> instanceMap = new HashMap<>();
|
|
+ private final Map<Class<?>, Object> overrides;
|
|
+ @SuppressWarnings("unchecked")
|
|
+ private final FieldDiscoverer<Map<Field, Object>> delegate = (FieldDiscoverer<Map<Field, Object>>) FieldDiscoverer.object(target -> {
|
|
+ final Class<?> type = erase(target.getType());
|
|
+ if (this.overrides().containsKey(type)) {
|
|
+ this.instanceMap.put(type, this.overrides().get(type));
|
|
+ return () -> this.overrides().get(type);
|
|
+ }
|
|
+ if (ConfigurationPart.class.isAssignableFrom(type) && !this.instanceMap.containsKey(type)) {
|
|
+ try {
|
|
+ final Constructor<?> constructor;
|
|
+ final CheckedSupplier<Object, ReflectiveOperationException> instanceSupplier;
|
|
+ if (type.getEnclosingClass() != null && !Modifier.isStatic(type.getModifiers())) {
|
|
+ final @Nullable Object instance = this.instanceMap.get(type.getEnclosingClass());
|
|
+ if (instance == null) {
|
|
+ throw new SerializationException("Cannot create a new instance of an inner class " + type.getName() + " without an instance of its enclosing class " + type.getEnclosingClass().getName());
|
|
+ }
|
|
+ constructor = type.getDeclaredConstructor(type.getEnclosingClass());
|
|
+ instanceSupplier = () -> constructor.newInstance(instance);
|
|
+ } else {
|
|
+ constructor = type.getDeclaredConstructor();
|
|
+ instanceSupplier = constructor::newInstance;
|
|
+ }
|
|
+ constructor.setAccessible(true);
|
|
+ final Object instance = instanceSupplier.get();
|
|
+ this.instanceMap.put(type, instance);
|
|
+ return () -> instance;
|
|
+ } catch (ReflectiveOperationException e) {
|
|
+ throw new SerializationException(ConfigurationPart.class, target + " must be a valid ConfigurationPart", e);
|
|
+ }
|
|
+ } else {
|
|
+ throw new SerializationException(target + " must be a valid ConfigurationPart");
|
|
+ }
|
|
+ }, "Object must be a unique ConfigurationPart");
|
|
+
|
|
+ InnerClassFieldDiscoverer(Map<Class<?>, Object> overrides) {
|
|
+ this.overrides = overrides;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public @Nullable <V> InstanceFactory<Map<Field, Object>> discover(AnnotatedType target, FieldCollector<Map<Field, Object>, V> collector) throws SerializationException {
|
|
+ final Class<?> clazz = erase(target.getType());
|
|
+ if (ConfigurationPart.class.isAssignableFrom(clazz)) {
|
|
+ final FieldDiscoverer.@Nullable InstanceFactory<Map<Field, Object>> instanceFactoryDelegate = this.delegate.<V>discover(target, (name, type, annotations, deserializer, serializer) -> {
|
|
+ if (!erase(type.getType()).equals(clazz.getEnclosingClass())) { // don't collect synth fields for inner classes
|
|
+ collector.accept(name, type, annotations, deserializer, serializer);
|
|
+ }
|
|
+ });
|
|
+ if (instanceFactoryDelegate instanceof FieldDiscoverer.MutableInstanceFactory<Map<Field, Object>> mutableInstanceFactoryDelegate) {
|
|
+ return new MutableInstanceFactory<>() {
|
|
+ @Override
|
|
+ public Map<Field, Object> begin() {
|
|
+ return mutableInstanceFactoryDelegate.begin();
|
|
+ }
|
|
+
|
|
+ @SuppressWarnings("unchecked")
|
|
+ @Override
|
|
+ public void complete(Object instance, Map<Field, Object> intermediate) throws SerializationException {
|
|
+ final Iterator<Map.Entry<Field, Object>> iter = intermediate.entrySet().iterator();
|
|
+ try {
|
|
+ while (iter.hasNext()) { // manually merge any mergeable maps
|
|
+ Map.Entry<Field, Object> entry = iter.next();
|
|
+ if (entry.getKey().isAnnotationPresent(MergeMap.class) && Map.class.isAssignableFrom(entry.getKey().getType()) && intermediate.get(entry.getKey()) instanceof Map<?, ?> map) {
|
|
+ iter.remove();
|
|
+ @Nullable Map<Object, Object> existingMap = (Map<Object, Object>) entry.getKey().get(instance);
|
|
+ if (existingMap != null) {
|
|
+ existingMap.putAll(map);
|
|
+ } else {
|
|
+ entry.getKey().set(instance, entry.getValue());
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ } catch (final IllegalAccessException e) {
|
|
+ throw new SerializationException(target.getType(), e);
|
|
+ }
|
|
+ mutableInstanceFactoryDelegate.complete(instance, intermediate);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public Object complete(Map<Field, Object> intermediate) throws SerializationException {
|
|
+ @Nullable Object targetInstance = InnerClassFieldDiscoverer.this.instanceMap.get(GenericTypeReflector.erase(target.getType()));
|
|
+ if (targetInstance != null) {
|
|
+ this.complete(targetInstance, intermediate);
|
|
+ } else {
|
|
+ targetInstance = mutableInstanceFactoryDelegate.complete(intermediate);
|
|
+ }
|
|
+ if (targetInstance instanceof ConfigurationPart.Post post) {
|
|
+ post.postProcess();
|
|
+ }
|
|
+ return targetInstance;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean canCreateInstances() {
|
|
+ return mutableInstanceFactoryDelegate.canCreateInstances();
|
|
+ }
|
|
+ };
|
|
+ }
|
|
+ }
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ private Map<Class<?>, Object> overrides() {
|
|
+ return this.overrides;
|
|
+ }
|
|
+
|
|
+ static FieldDiscoverer<?> worldConfig(Configurations.ContextMap contextMap) {
|
|
+ final Map<Class<?>, Object> overrides = Map.of(
|
|
+ WorldConfiguration.class, new WorldConfiguration(
|
|
+ contextMap.require(PaperConfigurations.SPIGOT_WORLD_CONFIG_CONTEXT_KEY).get(),
|
|
+ contextMap.require(Configurations.WORLD_KEY)
|
|
+ )
|
|
+ );
|
|
+ return new InnerClassFieldDiscoverer(overrides);
|
|
+ }
|
|
+
|
|
+ static FieldDiscoverer<?> globalConfig() {
|
|
+ return new InnerClassFieldDiscoverer(Collections.emptyMap());
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/MergeMap.java b/src/main/java/io/papermc/paper/configuration/MergeMap.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..a977b80cb196b7345bdfcb0b65ee2021f112efd1
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/configuration/MergeMap.java
|
|
@@ -0,0 +1,19 @@
|
|
+package io.papermc.paper.configuration;
|
|
+
|
|
+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;
|
|
+
|
|
+/**
|
|
+ * For use in maps inside {@link ConfigurationPart}s that have default keys that shouldn't be removed by users
|
|
+ * <p>
|
|
+ * Note that when the config is reloaded, the maps will be merged again, so make sure this map can't accumulate
|
|
+ * keys overtime.
|
|
+ */
|
|
+@Documented
|
|
+@Target(ElementType.FIELD)
|
|
+@Retention(RetentionPolicy.RUNTIME)
|
|
+public @interface MergeMap {
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/NestedSetting.java b/src/main/java/io/papermc/paper/configuration/NestedSetting.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..69add4a7f1147015806bc9b63a8340d1893356c1
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/configuration/NestedSetting.java
|
|
@@ -0,0 +1,32 @@
|
|
+package io.papermc.paper.configuration;
|
|
+
|
|
+import org.checkerframework.checker.nullness.qual.Nullable;
|
|
+import org.spongepowered.configurate.objectmapping.meta.NodeResolver;
|
|
+
|
|
+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;
|
|
+
|
|
+@Documented
|
|
+@Retention(RetentionPolicy.RUNTIME)
|
|
+@Target(ElementType.FIELD)
|
|
+public @interface NestedSetting {
|
|
+
|
|
+ String[] value();
|
|
+
|
|
+ class Factory implements NodeResolver.Factory {
|
|
+ @Override
|
|
+ public @Nullable NodeResolver make(String name, AnnotatedElement element) {
|
|
+ if (element.isAnnotationPresent(NestedSetting.class)) {
|
|
+ Object[] path = element.getAnnotation(NestedSetting.class).value();
|
|
+ if (path.length > 0) {
|
|
+ return node -> node.node(path);
|
|
+ }
|
|
+ }
|
|
+ return null;
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java b/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..e446542ff64670e368c8515d1716af5407f98c7a
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java
|
|
@@ -0,0 +1,414 @@
|
|
+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.serializer.ComponentSerializer;
|
|
+import io.papermc.paper.configuration.serializer.EnumValueSerializer;
|
|
+import io.papermc.paper.configuration.serializer.FastutilMapSerializer;
|
|
+import io.papermc.paper.configuration.serializer.PacketClassSerializer;
|
|
+import io.papermc.paper.configuration.serializer.StringRepresentableSerializer;
|
|
+import io.papermc.paper.configuration.serializer.TableSerializer;
|
|
+import io.papermc.paper.configuration.serializer.collections.MapSerializer;
|
|
+import io.papermc.paper.configuration.serializer.registry.RegistryHolderSerializer;
|
|
+import io.papermc.paper.configuration.serializer.registry.RegistryValueSerializer;
|
|
+import io.papermc.paper.configuration.transformation.Transformations;
|
|
+import io.papermc.paper.configuration.transformation.global.LegacyPaperConfig;
|
|
+import io.papermc.paper.configuration.transformation.world.FeatureSeedsGeneration;
|
|
+import io.papermc.paper.configuration.transformation.world.LegacyPaperWorldConfig;
|
|
+import io.papermc.paper.configuration.type.BooleanOrDefault;
|
|
+import io.papermc.paper.configuration.type.DoubleOrDefault;
|
|
+import io.papermc.paper.configuration.type.Duration;
|
|
+import io.papermc.paper.configuration.type.EngineMode;
|
|
+import io.papermc.paper.configuration.type.IntOrDefault;
|
|
+import io.papermc.paper.configuration.type.fallback.FallbackValueSerializer;
|
|
+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 net.minecraft.core.Registry;
|
|
+import net.minecraft.resources.ResourceLocation;
|
|
+import net.minecraft.server.MinecraftServer;
|
|
+import net.minecraft.server.level.ServerLevel;
|
|
+import net.minecraft.world.entity.EntityType;
|
|
+import net.minecraft.world.item.Item;
|
|
+import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
|
|
+import org.apache.commons.lang3.RandomStringUtils;
|
|
+import org.bukkit.command.Command;
|
|
+import org.bukkit.configuration.ConfigurationSection;
|
|
+import org.bukkit.configuration.file.YamlConfiguration;
|
|
+import org.jetbrains.annotations.VisibleForTesting;
|
|
+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.ObjectMapper;
|
|
+import org.spongepowered.configurate.transformation.ConfigurationTransformation;
|
|
+import org.spongepowered.configurate.transformation.TransformAction;
|
|
+import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
|
|
+
|
|
+import java.io.File;
|
|
+import java.io.IOException;
|
|
+import java.lang.reflect.Type;
|
|
+import java.nio.file.Files;
|
|
+import java.nio.file.Path;
|
|
+import java.nio.file.StandardCopyOption;
|
|
+import java.util.HashMap;
|
|
+import java.util.List;
|
|
+import java.util.Map;
|
|
+import java.util.function.Function;
|
|
+import java.util.function.Supplier;
|
|
+
|
|
+import static com.google.common.base.Preconditions.checkState;
|
|
+import static io.leangen.geantyref.GenericTypeReflector.erase;
|
|
+
|
|
+@SuppressWarnings("Convert2Diamond")
|
|
+public class PaperConfigurations extends Configurations<GlobalConfiguration, WorldConfiguration> {
|
|
+
|
|
+ private static final Logger LOGGER = LogUtils.getLogger();
|
|
+ static final String GLOBAL_CONFIG_FILE_NAME = "paper-global.yml";
|
|
+ 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.
|
|
+ As you can see, there's a lot 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.
|
|
+
|
|
+ The world configuration options have been moved inside
|
|
+ their respective world folder. The files are named %s
|
|
+
|
|
+ Docs: https://docs.papermc.io/
|
|
+ Discord: https://discord.gg/papermc
|
|
+ Website: https://papermc.io/""", WORLD_CONFIG_FILE_NAME);
|
|
+
|
|
+ private static final String WORLD_DEFAULTS_HEADER = """
|
|
+ This is the world defaults configuration file for Paper.
|
|
+ As you can see, there's a lot 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.
|
|
+
|
|
+ Configuration options here apply to all worlds, unless you specify overrides inside
|
|
+ the world-specific config file inside each world folder.
|
|
+
|
|
+ Docs: https://docs.papermc.io/
|
|
+ Discord: https://discord.gg/papermc
|
|
+ Website: https://papermc.io/""";
|
|
+
|
|
+ private static final Function<ContextMap, String> WORLD_HEADER = map -> String.format("""
|
|
+ This is a world configuration file for Paper.
|
|
+ This file may start empty but can be filled with settings to override ones in the %s/%s
|
|
+
|
|
+ World: %s (%s)""",
|
|
+ PaperConfigurations.CONFIG_DIR,
|
|
+ PaperConfigurations.WORLD_DEFAULTS_CONFIG_FILE_NAME,
|
|
+ map.require(WORLD_NAME),
|
|
+ map.require(WORLD_KEY)
|
|
+ );
|
|
+
|
|
+ private static final Supplier<SpigotWorldConfig> SPIGOT_WORLD_DEFAULTS = Suppliers.memoize(() -> new SpigotWorldConfig(RandomStringUtils.randomAlphabetic(255)) {
|
|
+ @Override // override to ensure "verbose" is false
|
|
+ public void init() {
|
|
+ SpigotConfig.readConfig(SpigotWorldConfig.class, this);
|
|
+ }
|
|
+ });
|
|
+ static final ContextKey<Supplier<SpigotWorldConfig>> SPIGOT_WORLD_CONFIG_CONTEXT_KEY = new ContextKey<>(new TypeToken<Supplier<SpigotWorldConfig>>() {}, "spigot world config");
|
|
+
|
|
+
|
|
+ public PaperConfigurations(final Path globalFolder) {
|
|
+ super(globalFolder, GlobalConfiguration.class, WorldConfiguration.class, GLOBAL_CONFIG_FILE_NAME, WORLD_DEFAULTS_CONFIG_FILE_NAME, WORLD_CONFIG_FILE_NAME);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected YamlConfigurationLoader.Builder createLoaderBuilder() {
|
|
+ return super.createLoaderBuilder()
|
|
+ .defaultOptions(PaperConfigurations::defaultOptions);
|
|
+ }
|
|
+
|
|
+ private static ConfigurationOptions defaultOptions(ConfigurationOptions options) {
|
|
+ return options.serializers(builder -> builder
|
|
+ .register(MapSerializer.TYPE, new MapSerializer(false))
|
|
+ .register(new EnumValueSerializer())
|
|
+ .register(new ComponentSerializer())
|
|
+ );
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected ObjectMapper.Factory.Builder createGlobalObjectMapperFactoryBuilder() {
|
|
+ return defaultGlobalFactoryBuilder(super.createGlobalObjectMapperFactoryBuilder());
|
|
+ }
|
|
+
|
|
+ private static ObjectMapper.Factory.Builder defaultGlobalFactoryBuilder(ObjectMapper.Factory.Builder builder) {
|
|
+ return builder.addDiscoverer(InnerClassFieldDiscoverer.globalConfig());
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected YamlConfigurationLoader.Builder createGlobalLoaderBuilder() {
|
|
+ return super.createGlobalLoaderBuilder()
|
|
+ .defaultOptions(PaperConfigurations::defaultGlobalOptions);
|
|
+ }
|
|
+
|
|
+ private static ConfigurationOptions defaultGlobalOptions(ConfigurationOptions options) {
|
|
+ return options
|
|
+ .header(GLOBAL_HEADER)
|
|
+ .serializers(builder -> builder.register(new PacketClassSerializer()));
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public GlobalConfiguration initializeGlobalConfiguration() throws ConfigurateException {
|
|
+ GlobalConfiguration configuration = super.initializeGlobalConfiguration();
|
|
+ GlobalConfiguration.set(configuration);
|
|
+ return configuration;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected ContextMap.Builder createDefaultContextMap() {
|
|
+ return super.createDefaultContextMap()
|
|
+ .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(contextMap));
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected YamlConfigurationLoader.Builder createWorldConfigLoaderBuilder(final ContextMap contextMap) {
|
|
+ return super.createWorldConfigLoaderBuilder(contextMap)
|
|
+ .defaultOptions(options -> options
|
|
+ .header(contextMap.require(WORLD_NAME).equals(WORLD_DEFAULTS) ? WORLD_DEFAULTS_HEADER : WORLD_HEADER.apply(contextMap))
|
|
+ .serializers(serializers -> serializers
|
|
+ .register(new TypeToken<Reference2IntMap<?>>() {}, new FastutilMapSerializer.SomethingToPrimitive<Reference2IntMap<?>>(Reference2IntOpenHashMap::new, Integer.TYPE))
|
|
+ .register(new TypeToken<Reference2LongMap<?>>() {}, new FastutilMapSerializer.SomethingToPrimitive<Reference2LongMap<?>>(Reference2LongOpenHashMap::new, Long.TYPE))
|
|
+ .register(new TypeToken<Table<?, ?, ?>>() {}, new TableSerializer())
|
|
+ .register(new StringRepresentableSerializer())
|
|
+ .register(IntOrDefault.SERIALIZER)
|
|
+ .register(DoubleOrDefault.SERIALIZER)
|
|
+ .register(BooleanOrDefault.SERIALIZER)
|
|
+ .register(Duration.SERIALIZER)
|
|
+ .register(EngineMode.SERIALIZER)
|
|
+ .register(FallbackValueSerializer.create(contextMap.require(SPIGOT_WORLD_CONFIG_CONTEXT_KEY).get(), MinecraftServer::getServer))
|
|
+ .register(new RegistryValueSerializer<>(new TypeToken<EntityType<?>>() {}, Registry.ENTITY_TYPE_REGISTRY, true))
|
|
+ .register(new RegistryValueSerializer<>(Item.class, Registry.ITEM_REGISTRY, true))
|
|
+ .register(new RegistryHolderSerializer<>(new TypeToken<ConfiguredFeature<?, ?>>() {}, Registry.CONFIGURED_FEATURE_REGISTRY, false))
|
|
+ .register(new RegistryHolderSerializer<>(Item.class, Registry.ITEM_REGISTRY, true))
|
|
+ )
|
|
+ );
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected void applyWorldConfigTransformations(final ContextMap contextMap, final ConfigurationNode node) throws ConfigurateException {
|
|
+ final ConfigurationNode version = node.node(Configuration.VERSION_FIELD);
|
|
+ final String world = contextMap.require(WORLD_NAME);
|
|
+ if (version.virtual()) {
|
|
+ LOGGER.warn("The world config file for " + world + " didn't have a version set, assuming latest");
|
|
+ version.raw(WorldConfiguration.CURRENT_VERSION);
|
|
+ }
|
|
+ ConfigurationTransformation.Builder builder = ConfigurationTransformation.builder();
|
|
+ for (NodePath path : RemovedConfigurations.REMOVED_WORLD_PATHS) {
|
|
+ builder.addAction(path, TransformAction.remove());
|
|
+ }
|
|
+ builder.build().apply(node);
|
|
+ // ADD FUTURE TRANSFORMS HERE
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected void applyGlobalConfigTransformations(ConfigurationNode node) throws ConfigurateException {
|
|
+ ConfigurationTransformation.Builder builder = ConfigurationTransformation.builder();
|
|
+ for (NodePath path : RemovedConfigurations.REMOVED_GLOBAL_PATHS) {
|
|
+ builder.addAction(path, TransformAction.remove());
|
|
+ }
|
|
+ builder.build().apply(node);
|
|
+ // ADD FUTURE TRANSFORMS HERE
|
|
+ }
|
|
+
|
|
+ private static final List<Transformations.DefaultsAware> DEFAULT_AWARE_TRANSFORMATIONS = List.of(FeatureSeedsGeneration::apply);
|
|
+
|
|
+ @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));
|
|
+
|
|
+ ConfigurationTransformation transformation;
|
|
+ try {
|
|
+ transformation = builder.build(); // build throws IAE if no actions were provided (bad zml)
|
|
+ } catch (IllegalArgumentException ignored) {
|
|
+ return;
|
|
+ }
|
|
+ transformation.apply(worldNode);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public WorldConfiguration createWorldConfig(final ContextMap contextMap) {
|
|
+ final String levelName = contextMap.require(WORLD_NAME);
|
|
+ try {
|
|
+ return super.createWorldConfig(contextMap);
|
|
+ } catch (IOException exception) {
|
|
+ throw new RuntimeException("Could not create world config for " + levelName, exception);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected boolean isConfigType(final Type type) {
|
|
+ return ConfigurationPart.class.isAssignableFrom(erase(type));
|
|
+ }
|
|
+
|
|
+ public void reloadConfigs(MinecraftServer server) {
|
|
+ try {
|
|
+ this.initializeGlobalConfiguration(reloader(this.globalConfigClass, GlobalConfiguration.get()));
|
|
+ this.initializeWorldDefaultsConfiguration();
|
|
+ for (ServerLevel level : server.getAllLevels()) {
|
|
+ this.createWorldConfig(createWorldContextMap(level), reloader(this.worldConfigClass, level.paperConfig()));
|
|
+ }
|
|
+ } catch (Exception ex) {
|
|
+ throw new RuntimeException("Could not reload paper configuration files", ex);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private static ContextMap createWorldContextMap(ServerLevel level) {
|
|
+ return createWorldContextMap(level.convertable.levelDirectory.path(), level.serverLevelData.getLevelName(), level.dimension().location(), level.spigotConfig);
|
|
+ }
|
|
+
|
|
+ public static ContextMap createWorldContextMap(Path dir, String levelName, ResourceLocation worldKey, SpigotWorldConfig spigotConfig) {
|
|
+ return ContextMap.builder()
|
|
+ .put(WORLD_DIRECTORY, dir)
|
|
+ .put(WORLD_NAME, levelName)
|
|
+ .put(WORLD_KEY, worldKey)
|
|
+ .put(SPIGOT_WORLD_CONFIG_CONTEXT_KEY, Suppliers.ofInstance(spigotConfig))
|
|
+ .build();
|
|
+ }
|
|
+
|
|
+ public static PaperConfigurations setup(final Path legacyConfig, final Path configDir, final Path worldFolder, final File spigotConfig) throws Exception {
|
|
+ if (needsConverting(legacyConfig)) {
|
|
+ try {
|
|
+ if (Files.exists(configDir) && !Files.isDirectory(configDir)) {
|
|
+ throw new RuntimeException("Paper needs to create a '" + CONFIG_DIR + "' folder in the root of your server. You already have a non-directory named '" + CONFIG_DIR + "'. 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 '" + CONFIG_DIR + "' folder. You already have a non-directory named '" + BACKUP_DIR + "'. Please remove it and restart the server.");
|
|
+ }
|
|
+ createDirectoriesSymlinkAware(backupDir);
|
|
+ final String backupFileName = legacyConfig.getFileName().toString() + ".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 '" + BACKUP_DIR + "' folder. You already have a non-file named '" + backupFileName + "'. Please remove it and restart the server.");
|
|
+ }
|
|
+ Files.move(legacyConfig, legacyConfigBackup, StandardCopyOption.REPLACE_EXISTING); // make backup
|
|
+ convert(legacyConfigBackup, configDir, worldFolder, spigotConfig);
|
|
+ } catch (final IOException ex) {
|
|
+ throw new RuntimeException("Could not convert '" + legacyConfig.getFileName().toString() + "' to the new configuration format", ex);
|
|
+ }
|
|
+ }
|
|
+ try {
|
|
+ createDirectoriesSymlinkAware(configDir);
|
|
+ return new PaperConfigurations(configDir);
|
|
+ } catch (final IOException ex) {
|
|
+ throw new RuntimeException("Could not setup PaperConfigurations", ex);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ 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__________");
|
|
+ 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()));
|
|
+ }
|
|
+ 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;
|
|
+ }
|
|
+
|
|
+ @VisibleForTesting
|
|
+ static ConfigurationNode createForTesting() {
|
|
+ ObjectMapper.Factory factory = defaultGlobalFactoryBuilder(ObjectMapper.factoryBuilder()).build();
|
|
+ ConfigurationOptions options = defaultGlobalOptions(defaultOptions(ConfigurationOptions.defaults()))
|
|
+ .serializers(builder -> builder.register(type -> ConfigurationPart.class.isAssignableFrom(erase(type)), factory.asTypeSerializer()));
|
|
+ return BasicConfigurationNode.root(options);
|
|
+ }
|
|
+
|
|
+ // Sym links are not correctly checked in createDirectories
|
|
+ static void createDirectoriesSymlinkAware(Path path) throws IOException {
|
|
+ if (!Files.isDirectory(path)) {
|
|
+ Files.createDirectories(path);
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/RemovedConfigurations.java b/src/main/java/io/papermc/paper/configuration/RemovedConfigurations.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..1bb16fc7598cd53e822d84b69d6a9727b37f484f
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/configuration/RemovedConfigurations.java
|
|
@@ -0,0 +1,63 @@
|
|
+package io.papermc.paper.configuration;
|
|
+
|
|
+import org.spongepowered.configurate.NodePath;
|
|
+
|
|
+import static org.spongepowered.configurate.NodePath.path;
|
|
+
|
|
+interface RemovedConfigurations {
|
|
+
|
|
+ NodePath[] REMOVED_WORLD_PATHS = {
|
|
+ path("elytra-hit-wall-damage"),
|
|
+ path("queue-light-updates"),
|
|
+ path("save-queue-limit-for-auto-save"),
|
|
+ path("max-chunk-sends-per-tick"),
|
|
+ path("max-chunk-gens-per-tick"),
|
|
+ path("fire-physics-event-for-redstone"),
|
|
+ path("fix-zero-tick-instant-grow-farms"),
|
|
+ path("bed-search-radius"),
|
|
+ path("lightning-strike-distance-limit"),
|
|
+ path("fix-wither-targeting-bug"),
|
|
+ path("remove-corrupt-tile-entities"),
|
|
+ path("allow-undead-horse-leashing"),
|
|
+ path("reset-arrow-despawn-timer-on-fall"),
|
|
+ path("seed-based-feature-search"),
|
|
+ path("seed-based-feature-search-loads-chunks"),
|
|
+ path("viewdistances.no-tick-view-distance"),
|
|
+ path("seed-based-feature-search"), // unneeded as of 1.18
|
|
+ path("seed-based-feature-search-loads-chunks"), // unneeded as of 1.18
|
|
+ path("reset-arrow-despawn-timer-on-fall"),
|
|
+ path("squid-spawn-height"),
|
|
+ path("viewdistances"),
|
|
+ path("use-alternate-fallingblock-onGround-detection"),
|
|
+ path("skip-entity-ticking-in-chunks-scheduled-for-unload"),
|
|
+ path("tracker-update-distance"),
|
|
+ path("allow-block-location-tab-completion"),
|
|
+ path("cache-chunk-maps"),
|
|
+ path("disable-mood-sounds"),
|
|
+ path("fix-cannons"),
|
|
+ path("player-blocking-damage-multiplier"),
|
|
+ path("remove-invalid-mob-spawner-tile-entities"),
|
|
+ path("use-hopper-check"),
|
|
+ path("use-async-lighting"),
|
|
+ path("tnt-explosion-volume"),
|
|
+ path("entities", "spawning", "despawn-ranges", "soft"),
|
|
+ path("entities", "spawning", "despawn-ranges", "hard")
|
|
+ };
|
|
+
|
|
+ NodePath[] REMOVED_GLOBAL_PATHS = {
|
|
+ path("queue-light-updates-max-loss"),
|
|
+ path("sleep-between-chunk-saves"),
|
|
+ path("remove-invalid-statistics"),
|
|
+ path("min-chunk-load-threads"),
|
|
+ path("use-versioned-world"),
|
|
+ path("save-player-data"), // to spigot (converted)
|
|
+ path("log-named-entity-deaths"), // default in vanilla
|
|
+ path("chunk-tasks-per-tick"), // removed in tuinity merge
|
|
+ path("item-validation", "loc-name"),
|
|
+ path("commandErrorMessage"),
|
|
+ path("baby-zombie-movement-speed"),
|
|
+ path("limit-player-interactions"),
|
|
+ path("warnWhenSettingExcessiveVelocity")
|
|
+ };
|
|
+
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java b/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..e2c612dd55fcb2769fb06f7878b8d0873f2be139
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java
|
|
@@ -0,0 +1,467 @@
|
|
+package io.papermc.paper.configuration;
|
|
+
|
|
+import com.google.common.collect.HashBasedTable;
|
|
+import com.google.common.collect.Table;
|
|
+import com.mojang.logging.LogUtils;
|
|
+import io.papermc.paper.configuration.constraint.Constraint;
|
|
+import io.papermc.paper.configuration.constraint.Constraints;
|
|
+import io.papermc.paper.configuration.legacy.MaxEntityCollisionsInitializer;
|
|
+import io.papermc.paper.configuration.legacy.RequiresSpigotInitialization;
|
|
+import io.papermc.paper.configuration.legacy.SpawnLoadedRangeInitializer;
|
|
+import io.papermc.paper.configuration.transformation.world.FeatureSeedsGeneration;
|
|
+import io.papermc.paper.configuration.type.BooleanOrDefault;
|
|
+import io.papermc.paper.configuration.type.DoubleOrDefault;
|
|
+import io.papermc.paper.configuration.type.Duration;
|
|
+import io.papermc.paper.configuration.type.EngineMode;
|
|
+import io.papermc.paper.configuration.type.IntOrDefault;
|
|
+import io.papermc.paper.configuration.type.fallback.ArrowDespawnRate;
|
|
+import io.papermc.paper.configuration.type.fallback.AutosavePeriod;
|
|
+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 net.minecraft.Util;
|
|
+import net.minecraft.core.Holder;
|
|
+import net.minecraft.core.Registry;
|
|
+import net.minecraft.resources.ResourceLocation;
|
|
+import net.minecraft.world.Difficulty;
|
|
+import net.minecraft.world.entity.EntityType;
|
|
+import net.minecraft.world.entity.MobCategory;
|
|
+import net.minecraft.world.entity.monster.Vindicator;
|
|
+import net.minecraft.world.entity.monster.Zombie;
|
|
+import net.minecraft.world.item.Item;
|
|
+import net.minecraft.world.item.Items;
|
|
+import net.minecraft.world.level.NaturalSpawner;
|
|
+import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
|
|
+import org.slf4j.Logger;
|
|
+import org.spigotmc.SpigotWorldConfig;
|
|
+import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
|
+import org.spongepowered.configurate.objectmapping.meta.Required;
|
|
+import org.spongepowered.configurate.objectmapping.meta.Setting;
|
|
+
|
|
+import java.util.Arrays;
|
|
+import java.util.List;
|
|
+import java.util.Map;
|
|
+import java.util.function.Function;
|
|
+import java.util.stream.Collectors;
|
|
+
|
|
+@SuppressWarnings({"FieldCanBeLocal", "FieldMayBeFinal", "NotNullFieldNotInitialized", "InnerClassMayBeStatic"})
|
|
+public class WorldConfiguration extends ConfigurationPart {
|
|
+ private static final Logger LOGGER = LogUtils.getLogger();
|
|
+ static final int CURRENT_VERSION = 28;
|
|
+
|
|
+ private transient final SpigotWorldConfig spigotConfig;
|
|
+ private transient final ResourceLocation worldKey;
|
|
+ WorldConfiguration(SpigotWorldConfig spigotConfig, ResourceLocation worldKey) {
|
|
+ this.spigotConfig = spigotConfig;
|
|
+ this.worldKey = worldKey;
|
|
+ }
|
|
+
|
|
+ public boolean isDefault() {
|
|
+ return this.worldKey.equals(PaperConfigurations.WORLD_DEFAULTS_KEY);
|
|
+ }
|
|
+
|
|
+ @Setting(Configuration.VERSION_FIELD)
|
|
+ public int version = CURRENT_VERSION;
|
|
+
|
|
+ public Anticheat anticheat;
|
|
+
|
|
+ public class Anticheat extends ConfigurationPart {
|
|
+
|
|
+ public Obfuscation obfuscation;
|
|
+
|
|
+ public class Obfuscation extends ConfigurationPart {
|
|
+ public Items items = new Items();
|
|
+ public class Items extends ConfigurationPart {
|
|
+ public boolean hideItemmeta = false;
|
|
+ public boolean hideDurability = false;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public AntiXray antiXray;
|
|
+
|
|
+ public class AntiXray extends ConfigurationPart {
|
|
+ public boolean enabled = false;
|
|
+ public EngineMode engineMode = EngineMode.HIDE;
|
|
+ public int maxBlockHeight = 64;
|
|
+ public int updateRadius = 2;
|
|
+ public boolean lavaObscures = false;
|
|
+ public boolean usePermission = false;
|
|
+ public List<String> hiddenBlocks = List.of("copper_ore", "deepslate_copper_ore", "gold_ore", "deepslate_gold_ore", "iron_ore", "deepslate_iron_ore",
|
|
+ "coal_ore", "deepslate_coal_ore", "lapis_ore", "deepslate_lapis_ore", "mossy_cobblestone", "obsidian", "chest", "diamond_ore", "deepslate_diamond_ore",
|
|
+ "redstone_ore", "deepslate_redstone_ore", "clay", "emerald_ore", "deepslate_emerald_ore", "ender_chest"); // TODO update type to List<Block>
|
|
+ public List<String> replacementBlocks = List.of("stone", "oak_planks", "deepslate"); // TODO update type to List<Block>
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public Entities entities;
|
|
+
|
|
+ public class Entities extends ConfigurationPart {
|
|
+ public boolean entitiesTargetWithFollowRange = false;
|
|
+ public MobEffects mobEffects;
|
|
+
|
|
+ public class MobEffects extends ConfigurationPart {
|
|
+ public boolean undeadImmuneToCertainEffects = true;
|
|
+ public boolean spidersImmuneToPoisonEffect = true;
|
|
+ public ImmuneToWitherEffect immuneToWitherEffect;
|
|
+
|
|
+ public class ImmuneToWitherEffect extends ConfigurationPart {
|
|
+ public boolean wither = true;
|
|
+ public boolean witherSkeleton = true;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public ArmorStands armorStands;
|
|
+
|
|
+ public class ArmorStands extends ConfigurationPart {
|
|
+ public boolean doCollisionEntityLookups = true;
|
|
+ public boolean tick = true;
|
|
+ }
|
|
+
|
|
+ public Spawning spawning;
|
|
+
|
|
+ public class Spawning extends ConfigurationPart {
|
|
+ public ArrowDespawnRate nonPlayerArrowDespawnRate = ArrowDespawnRate.def(WorldConfiguration.this.spigotConfig);
|
|
+ public ArrowDespawnRate creativeArrowDespawnRate = ArrowDespawnRate.def(WorldConfiguration.this.spigotConfig);
|
|
+ public boolean filterNbtDataFromSpawnEggsAndRelated = true;
|
|
+ public boolean disableMobSpawnerSpawnEggTransformation = false;
|
|
+ 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)));
|
|
+ @MergeMap
|
|
+ public Map<MobCategory, DespawnRange> despawnRanges = Arrays.stream(MobCategory.values()).collect(Collectors.toMap(Function.identity(), category -> new DespawnRange(category.getNoDespawnDistance(), category.getDespawnDistance())));
|
|
+
|
|
+ @ConfigSerializable
|
|
+ public record DespawnRange(@Required int soft, @Required int hard) {
|
|
+ }
|
|
+
|
|
+ public WaterAnimalSpawnHeight wateranimalSpawnHeight;
|
|
+
|
|
+ public class WaterAnimalSpawnHeight extends ConfigurationPart {
|
|
+ public IntOrDefault maximum = IntOrDefault.USE_DEFAULT;
|
|
+ public IntOrDefault minimum = IntOrDefault.USE_DEFAULT;
|
|
+ }
|
|
+
|
|
+ public SlimeSpawnHeight slimeSpawnHeight;
|
|
+
|
|
+ public class SlimeSpawnHeight extends ConfigurationPart {
|
|
+
|
|
+ public SurfaceSpawnableSlimeBiome surfaceBiome;
|
|
+
|
|
+ public class SurfaceSpawnableSlimeBiome extends ConfigurationPart {
|
|
+ public double maximum = 70;
|
|
+ public double minimum = 50;
|
|
+ }
|
|
+
|
|
+ public SlimeChunk slimeChunk;
|
|
+
|
|
+ public class SlimeChunk extends ConfigurationPart {
|
|
+ public double maximum = 40;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public WanderingTrader wanderingTrader;
|
|
+
|
|
+ public class WanderingTrader extends ConfigurationPart {
|
|
+ public int spawnMinuteLength = 1200;
|
|
+ public int spawnDayLength = 24000;
|
|
+ public int spawnChanceFailureIncrement = 25;
|
|
+ public int spawnChanceMin = 25;
|
|
+ public int spawnChanceMax = 75;
|
|
+ }
|
|
+
|
|
+ public boolean allChunksAreSlimeChunks = false;
|
|
+ @Constraint(Constraints.BelowZeroDoubleToDefault.class)
|
|
+ public DoubleOrDefault skeletonHorseThunderSpawnChance = DoubleOrDefault.USE_DEFAULT;
|
|
+ public boolean ironGolemsCanSpawnInAir = false;
|
|
+ public boolean countAllMobsForSpawning = false;
|
|
+ public int monsterSpawnMaxLightLevel = -1;
|
|
+ public DuplicateUUID duplicateUuid;
|
|
+
|
|
+ public class DuplicateUUID extends ConfigurationPart {
|
|
+ public DuplicateUUIDMode mode = DuplicateUUIDMode.SAFE_REGEN;
|
|
+ public int safeRegenDeleteRange = 32;
|
|
+
|
|
+ public enum DuplicateUUIDMode {
|
|
+ SAFE_REGEN, DELETE, NOTHING, WARN;
|
|
+ }
|
|
+ }
|
|
+ public AltItemDespawnRate altItemDespawnRate;
|
|
+
|
|
+ public class AltItemDespawnRate extends ConfigurationPart {
|
|
+ public boolean enabled = false;
|
|
+ public Reference2IntMap<Item> items = new Reference2IntOpenHashMap<>(Map.of(Items.COBBLESTONE, 300));
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public Behavior behavior;
|
|
+
|
|
+ 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;
|
|
+ public boolean piglinsGuardChests = true;
|
|
+ public double babyZombieMovementModifier = 0.5;
|
|
+ public DoorBreakingDifficulty doorBreakingDifficulty;
|
|
+
|
|
+ public class DoorBreakingDifficulty extends ConfigurationPart { // TODO convert to map at some point
|
|
+ public List<Difficulty> zombie = Arrays.stream(Difficulty.values()).filter(Zombie.DOOR_BREAKING_PREDICATE).toList();
|
|
+ public List<Difficulty> husk = Arrays.stream(Difficulty.values()).filter(Zombie.DOOR_BREAKING_PREDICATE).toList();
|
|
+ @Setting("zombie_villager")
|
|
+ public List<Difficulty> zombieVillager = Arrays.stream(Difficulty.values()).filter(Zombie.DOOR_BREAKING_PREDICATE).toList();
|
|
+ @Setting("zombified_piglin")
|
|
+ public List<Difficulty> zombified_piglin = Arrays.stream(Difficulty.values()).filter(Zombie.DOOR_BREAKING_PREDICATE).toList();
|
|
+ public List<Difficulty> vindicator = Arrays.stream(Difficulty.values()).filter(Vindicator.DOOR_BREAKING_PREDICATE).toList();
|
|
+
|
|
+ // TODO remove when this becomes a proper map
|
|
+ public List<Difficulty> get(EntityType<?> type) {
|
|
+ return this.getOrDefault(type, null);
|
|
+ }
|
|
+
|
|
+ public List<Difficulty> getOrDefault(EntityType<?> type, List<Difficulty> fallback) {
|
|
+ if (type == EntityType.ZOMBIE) {
|
|
+ return this.zombie;
|
|
+ } else if (type == EntityType.HUSK) {
|
|
+ return this.husk;
|
|
+ } else if (type == EntityType.ZOMBIE_VILLAGER) {
|
|
+ return this.zombieVillager;
|
|
+ } else if (type == EntityType.ZOMBIFIED_PIGLIN) {
|
|
+ return this.zombified_piglin;
|
|
+ } else if (type == EntityType.VINDICATOR) {
|
|
+ return this.vindicator;
|
|
+ } else {
|
|
+ return fallback;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public boolean disableCreeperLingeringEffect = false;
|
|
+ public boolean enderDragonsDeathAlwaysPlacesDragonEgg = false;
|
|
+ public boolean phantomsDoNotSpawnOnCreativePlayers = true;
|
|
+ public boolean phantomsOnlyAttackInsomniacs = true;
|
|
+ public boolean parrotsAreUnaffectedByPlayerMovement = false;
|
|
+ public double zombieVillagerInfectionChance = -1.0;
|
|
+ public MobsCanAlwaysPickUpLoot mobsCanAlwaysPickUpLoot;
|
|
+
|
|
+ public class MobsCanAlwaysPickUpLoot extends ConfigurationPart {
|
|
+ public boolean zombies = false;
|
|
+ public boolean skeletons = false;
|
|
+ }
|
|
+
|
|
+ public boolean disablePlayerCrits = false;
|
|
+ public boolean nerfPigmenFromNetherPortals = false;
|
|
+ public PillagerPatrols pillagerPatrols;
|
|
+
|
|
+ public class PillagerPatrols extends ConfigurationPart {
|
|
+ public boolean disable = false;
|
|
+ public double spawnChance = 0.2;
|
|
+ public SpawnDelay spawnDelay;
|
|
+ public Start start;
|
|
+
|
|
+ public class SpawnDelay extends ConfigurationPart {
|
|
+ public boolean perPlayer = false;
|
|
+ public int ticks = 12000;
|
|
+ }
|
|
+
|
|
+ public class Start extends ConfigurationPart {
|
|
+ public boolean perPlayer = false;
|
|
+ public int day = 5;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public Lootables lootables;
|
|
+
|
|
+ public class Lootables extends ConfigurationPart {
|
|
+ public boolean autoReplenish = false;
|
|
+ public boolean restrictPlayerReloot = true;
|
|
+ public boolean resetSeedOnFill = true;
|
|
+ public int maxRefills = -1;
|
|
+ public Duration refreshMin = Duration.of("12h");
|
|
+ public Duration refreshMax = Duration.of("2d");
|
|
+ }
|
|
+
|
|
+ public MaxGrowthHeight maxGrowthHeight;
|
|
+
|
|
+ public class MaxGrowthHeight extends ConfigurationPart {
|
|
+ public int cactus = 3;
|
|
+ public int reeds = 3;
|
|
+ public Bamboo bamboo;
|
|
+
|
|
+ public class Bamboo extends ConfigurationPart {
|
|
+ public int max = 16;
|
|
+ public int min = 11;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public Scoreboards scoreboards;
|
|
+
|
|
+ public class Scoreboards extends ConfigurationPart {
|
|
+ public boolean allowNonPlayerEntitiesOnScoreboards = false;
|
|
+ public boolean useVanillaWorldScoreboardNameColoring = false;
|
|
+ }
|
|
+
|
|
+ public Environment environment;
|
|
+
|
|
+ public class Environment extends ConfigurationPart {
|
|
+ public boolean disableThunder = false;
|
|
+ public boolean disableIceAndSnow = false;
|
|
+ public boolean optimizeExplosions = false;
|
|
+ public boolean disableExplosionKnockback = false;
|
|
+ public boolean generateFlatBedrock = false;
|
|
+ public FrostedIce frostedIce;
|
|
+
|
|
+ public class FrostedIce extends ConfigurationPart {
|
|
+ public boolean enabled = true;
|
|
+ public Delay delay;
|
|
+
|
|
+ public class Delay extends ConfigurationPart {
|
|
+ public int min = 20;
|
|
+ public int max = 40;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public TreasureMaps treasureMaps;
|
|
+ public class TreasureMaps extends ConfigurationPart {
|
|
+ public boolean enabled = true;
|
|
+ @NestedSetting({"find-already-discovered", "villager-trade"})
|
|
+ public boolean findAlreadyDiscoveredVillager = false;
|
|
+ @NestedSetting({"find-already-discovered", "loot-tables"})
|
|
+ public BooleanOrDefault findAlreadyDiscoveredLootTable = BooleanOrDefault.USE_DEFAULT;
|
|
+ }
|
|
+
|
|
+ public int waterOverLavaFlowSpeed = 5;
|
|
+ public int portalSearchRadius = 128;
|
|
+ public int portalCreateRadius = 16;
|
|
+ public boolean portalSearchVanillaDimensionScaling = true;
|
|
+ public boolean disableTeleportationSuffocationCheck = false;
|
|
+ public int netherCeilingVoidDamageHeight = 0;
|
|
+ }
|
|
+
|
|
+ public Spawn spawn;
|
|
+
|
|
+ public class Spawn extends ConfigurationPart {
|
|
+ @RequiresSpigotInitialization(SpawnLoadedRangeInitializer.class)
|
|
+ public short keepSpawnLoadedRange = 10;
|
|
+ public boolean keepSpawnLoaded = true;
|
|
+ public boolean allowUsingSignsInsideSpawnProtection = false;
|
|
+ }
|
|
+
|
|
+ public Maps maps;
|
|
+
|
|
+ public class Maps extends ConfigurationPart {
|
|
+ public int itemFrameCursorLimit = 128;
|
|
+ public int itemFrameCursorUpdateInterval = 10;
|
|
+ }
|
|
+
|
|
+ public Fixes fixes;
|
|
+
|
|
+ public class Fixes extends ConfigurationPart {
|
|
+ public boolean fixItemsMergingThroughWalls = false;
|
|
+ public boolean disableUnloadedChunkEnderpearlExploit = true;
|
|
+ public boolean preventTntFromMovingInWater = false;
|
|
+ public boolean splitOverstackedLoot = true;
|
|
+ public boolean fixCuringZombieVillagerDiscountExploit = true;
|
|
+ public int fallingBlockHeightNerf = 0;
|
|
+ public int tntEntityHeightNerf = 0;
|
|
+ }
|
|
+
|
|
+ public UnsupportedSettings unsupportedSettings;
|
|
+
|
|
+ public class UnsupportedSettings extends ConfigurationPart {
|
|
+ public boolean fixInvulnerableEndCrystalExploit = true;
|
|
+ }
|
|
+
|
|
+ public Hopper hopper;
|
|
+
|
|
+ public class Hopper extends ConfigurationPart {
|
|
+ public boolean cooldownWhenFull = true;
|
|
+ public boolean disableMoveEvent = false;
|
|
+ public boolean ignoreOccludingBlocks = false;
|
|
+ }
|
|
+
|
|
+ public Collisions collisions;
|
|
+
|
|
+ public class Collisions 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;
|
|
+ }
|
|
+
|
|
+ public Chunks chunks;
|
|
+
|
|
+ public class Chunks extends ConfigurationPart {
|
|
+ public AutosavePeriod autoSaveInterval = AutosavePeriod.def();
|
|
+ public int maxAutoSaveChunksPerTick = 24;
|
|
+ public int fixedChunkInhabitedTime = -1;
|
|
+ public boolean preventMovingIntoUnloadedChunks = false;
|
|
+ public Duration delayChunkUnloadsBy = Duration.of("10s");
|
|
+ public Reference2IntMap<EntityType<?>> entityPerChunkSaveLimit = Util.make(new Reference2IntOpenHashMap<>(Registry.ENTITY_TYPE.size()), map -> {
|
|
+ map.defaultReturnValue(-1);
|
|
+ map.put(EntityType.EXPERIENCE_ORB, -1);
|
|
+ map.put(EntityType.SNOWBALL, -1);
|
|
+ map.put(EntityType.ENDER_PEARL, -1);
|
|
+ map.put(EntityType.ARROW, -1);
|
|
+ map.put(EntityType.FIREBALL, -1);
|
|
+ map.put(EntityType.SMALL_FIREBALL, -1);
|
|
+ });
|
|
+ }
|
|
+
|
|
+ public FishingTimeRange fishingTimeRange;
|
|
+
|
|
+ public class FishingTimeRange extends ConfigurationPart {
|
|
+ public int minimum = 100;
|
|
+ public int maximum = 600;
|
|
+ }
|
|
+
|
|
+ public TickRates tickRates;
|
|
+
|
|
+ public class TickRates extends ConfigurationPart {
|
|
+ public int grassSpread = 1;
|
|
+ public int containerUpdate = 1;
|
|
+ public int mobSpawner = 1;
|
|
+ public Table<EntityType<?>, String, Integer> sensor = Util.make(HashBasedTable.create(), table -> table.put(EntityType.VILLAGER, "secondarypoisensor", 40));
|
|
+ public Table<EntityType<?>, String, Integer> behavior = Util.make(HashBasedTable.create(), table -> table.put(EntityType.VILLAGER, "validatenearbypoi", -1));
|
|
+ }
|
|
+
|
|
+ @Setting(FeatureSeedsGeneration.FEATURE_SEEDS_KEY)
|
|
+ public FeatureSeeds featureSeeds;
|
|
+
|
|
+ public class FeatureSeeds extends ConfigurationPart.Post {
|
|
+ @Setting(FeatureSeedsGeneration.GENERATE_KEY)
|
|
+ public boolean generateRandomSeedsForAll = false;
|
|
+ @Setting(FeatureSeedsGeneration.FEATURES_KEY)
|
|
+ public Reference2LongMap<Holder<ConfiguredFeature<?, ?>>> features = new Reference2LongOpenHashMap<>();
|
|
+
|
|
+ @Override
|
|
+ public void postProcess() {
|
|
+ this.features.defaultReturnValue(-1);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public Misc misc;
|
|
+
|
|
+ public class Misc extends ConfigurationPart {
|
|
+ public int lightQueueSize = 20;
|
|
+ public boolean updatePathfindingOnBlockUpdate = true;
|
|
+ public boolean showSignClickCommandFailureMsgsToPlayer = false;
|
|
+ public RedstoneImplementation redstoneImplementation = RedstoneImplementation.VANILLA;
|
|
+ public boolean disableEndCredits = false;
|
|
+ public float maxLeashDistance = 10f;
|
|
+ public boolean disableSprintInterruptionOnAttack = false;
|
|
+ public int shieldBlockingDelay = 5;
|
|
+ public boolean disableRelativeProjectileVelocity = false;
|
|
+
|
|
+ public enum RedstoneImplementation {
|
|
+ VANILLA, EIGENCRAFT, ALTERNATE_CURRENT
|
|
+ }
|
|
+ }
|
|
+
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/constraint/Constraint.java b/src/main/java/io/papermc/paper/configuration/constraint/Constraint.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..514be9a11e2ca368ea72dd2bac1b84bff5468814
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/configuration/constraint/Constraint.java
|
|
@@ -0,0 +1,30 @@
|
|
+package io.papermc.paper.configuration.constraint;
|
|
+
|
|
+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.Constructor;
|
|
+import java.lang.reflect.Type;
|
|
+
|
|
+@Documented
|
|
+@Retention(RetentionPolicy.RUNTIME)
|
|
+@Target({ElementType.FIELD, ElementType.TYPE, ElementType.PARAMETER})
|
|
+public @interface Constraint {
|
|
+ Class<? extends org.spongepowered.configurate.objectmapping.meta.Constraint<?>> value();
|
|
+
|
|
+ class Factory implements org.spongepowered.configurate.objectmapping.meta.Constraint.Factory<Constraint, Object> {
|
|
+ @SuppressWarnings("unchecked")
|
|
+ @Override
|
|
+ public org.spongepowered.configurate.objectmapping.meta.Constraint<Object> make(final Constraint data, final Type type) {
|
|
+ try {
|
|
+ final Constructor<? extends org.spongepowered.configurate.objectmapping.meta.Constraint<?>> constructor = data.value().getDeclaredConstructor();
|
|
+ constructor.trySetAccessible();
|
|
+ return (org.spongepowered.configurate.objectmapping.meta.Constraint<Object>) constructor.newInstance();
|
|
+ } catch (final ReflectiveOperationException e) {
|
|
+ throw new RuntimeException("Could not create constraint", e);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/constraint/Constraints.java b/src/main/java/io/papermc/paper/configuration/constraint/Constraints.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..b470332f542c30c42355adb711ff148e8e1dd7a1
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/configuration/constraint/Constraints.java
|
|
@@ -0,0 +1,74 @@
|
|
+package io.papermc.paper.configuration.constraint;
|
|
+
|
|
+import com.mojang.logging.LogUtils;
|
|
+import io.papermc.paper.configuration.GlobalConfiguration;
|
|
+import io.papermc.paper.configuration.type.DoubleOrDefault;
|
|
+import org.checkerframework.checker.nullness.qual.Nullable;
|
|
+import org.slf4j.Logger;
|
|
+import org.spongepowered.configurate.objectmapping.meta.Constraint;
|
|
+import org.spongepowered.configurate.serialize.SerializationException;
|
|
+
|
|
+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.util.OptionalDouble;
|
|
+
|
|
+public final class Constraints {
|
|
+ private Constraints() {
|
|
+ }
|
|
+
|
|
+ public static final class Velocity implements Constraint<GlobalConfiguration.Proxies.Velocity> {
|
|
+
|
|
+ private static final Logger LOGGER = LogUtils.getLogger();
|
|
+
|
|
+ @Override
|
|
+ public void validate(final GlobalConfiguration.Proxies.@Nullable Velocity value) throws SerializationException {
|
|
+ if (value != null && value.enabled && value.secret.isEmpty()) {
|
|
+ LOGGER.error("Velocity is enabled, but no secret key was specified. A secret key is required. Disabling velocity...");
|
|
+ value.enabled = false;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public static final class Positive implements Constraint<Number> {
|
|
+ @Override
|
|
+ public void validate(@Nullable Number value) throws SerializationException {
|
|
+ if (value != null && value.doubleValue() <= 0) {
|
|
+ throw new SerializationException(value + " should be positive");
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public static final class BelowZeroDoubleToDefault implements Constraint<DoubleOrDefault> {
|
|
+ @Override
|
|
+ public void validate(final @Nullable DoubleOrDefault container) {
|
|
+ if (container != null) {
|
|
+ final OptionalDouble value = container.value();
|
|
+ if (value.isPresent() && value.getAsDouble() < 0) {
|
|
+ container.value(OptionalDouble.empty());
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @Documented
|
|
+ @Retention(RetentionPolicy.RUNTIME)
|
|
+ @Target(ElementType.FIELD)
|
|
+ public @interface Min {
|
|
+ int value();
|
|
+
|
|
+ final class Factory implements Constraint.Factory<Min, Number> {
|
|
+ @Override
|
|
+ public Constraint<Number> make(Min data, Type type) {
|
|
+ return value -> {
|
|
+ if (value != null && value.intValue() < data.value()) {
|
|
+ throw new SerializationException(value + " is less than the min " + data.value());
|
|
+ }
|
|
+ };
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/legacy/MaxEntityCollisionsInitializer.java b/src/main/java/io/papermc/paper/configuration/legacy/MaxEntityCollisionsInitializer.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..62b43280f59163f7910f79cc901b50d05cdd024e
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/configuration/legacy/MaxEntityCollisionsInitializer.java
|
|
@@ -0,0 +1,29 @@
|
|
+package io.papermc.paper.configuration.legacy;
|
|
+
|
|
+import org.checkerframework.checker.nullness.qual.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;
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/legacy/RequiresSpigotInitialization.java b/src/main/java/io/papermc/paper/configuration/legacy/RequiresSpigotInitialization.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..611bdbcef3d52e09179aa8b1677ab1e198c70b02
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/configuration/legacy/RequiresSpigotInitialization.java
|
|
@@ -0,0 +1,51 @@
|
|
+package io.papermc.paper.configuration.legacy;
|
|
+
|
|
+import com.google.common.collect.HashBasedTable;
|
|
+import com.google.common.collect.Table;
|
|
+import org.checkerframework.checker.nullness.qual.Nullable;
|
|
+import org.spigotmc.SpigotWorldConfig;
|
|
+import org.spongepowered.configurate.objectmapping.meta.NodeResolver;
|
|
+
|
|
+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 java.util.Map;
|
|
+import java.util.concurrent.ConcurrentHashMap;
|
|
+
|
|
+@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;
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/legacy/SpawnLoadedRangeInitializer.java b/src/main/java/io/papermc/paper/configuration/legacy/SpawnLoadedRangeInitializer.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..fe5cc1c097f8d8c135e6ead6f458426bb84a8ebe
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/configuration/legacy/SpawnLoadedRangeInitializer.java
|
|
@@ -0,0 +1,27 @@
|
|
+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;
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/package-info.java b/src/main/java/io/papermc/paper/configuration/package-info.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..4e3bcd7c478096384fcc643d48771ab94318deb3
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/configuration/package-info.java
|
|
@@ -0,0 +1,5 @@
|
|
+@DefaultQualifier(NonNull.class)
|
|
+package io.papermc.paper.configuration;
|
|
+
|
|
+import org.checkerframework.checker.nullness.qual.NonNull;
|
|
+import org.checkerframework.framework.qual.DefaultQualifier;
|
|
\ No newline at end of file
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/serializer/ComponentSerializer.java b/src/main/java/io/papermc/paper/configuration/serializer/ComponentSerializer.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..9c339ef178ebc3b0251095f320e4a7a3656d3521
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/configuration/serializer/ComponentSerializer.java
|
|
@@ -0,0 +1,26 @@
|
|
+package io.papermc.paper.configuration.serializer;
|
|
+
|
|
+import net.kyori.adventure.text.Component;
|
|
+import net.kyori.adventure.text.minimessage.MiniMessage;
|
|
+import org.spongepowered.configurate.serialize.ScalarSerializer;
|
|
+import org.spongepowered.configurate.serialize.SerializationException;
|
|
+
|
|
+import java.lang.reflect.Type;
|
|
+import java.util.function.Predicate;
|
|
+
|
|
+public class ComponentSerializer extends ScalarSerializer<Component> {
|
|
+
|
|
+ public ComponentSerializer() {
|
|
+ super(Component.class);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public Component deserialize(Type type, Object obj) throws SerializationException {
|
|
+ return MiniMessage.miniMessage().deserialize(obj.toString());
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected Object serialize(Component component, Predicate<Class<?>> typeSupported) {
|
|
+ return MiniMessage.miniMessage().serialize(component);
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/serializer/EngineModeSerializer.java b/src/main/java/io/papermc/paper/configuration/serializer/EngineModeSerializer.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..27c0679d376bb31ab52131dfea74b3b580ca92b5
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/configuration/serializer/EngineModeSerializer.java
|
|
@@ -0,0 +1,33 @@
|
|
+package io.papermc.paper.configuration.serializer;
|
|
+
|
|
+import io.papermc.paper.configuration.type.EngineMode;
|
|
+import org.spongepowered.configurate.serialize.ScalarSerializer;
|
|
+import org.spongepowered.configurate.serialize.SerializationException;
|
|
+
|
|
+import java.lang.reflect.Type;
|
|
+import java.util.function.Predicate;
|
|
+
|
|
+public final class EngineModeSerializer extends ScalarSerializer<EngineMode> {
|
|
+
|
|
+ public EngineModeSerializer() {
|
|
+ super(EngineMode.class);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public EngineMode deserialize(Type type, Object obj) throws SerializationException {
|
|
+ if (obj instanceof Integer id) {
|
|
+ try {
|
|
+ return EngineMode.valueOf(id);
|
|
+ } catch (IllegalArgumentException e) {
|
|
+ throw new SerializationException(id + " is not a valid id for type " + type + " for this node");
|
|
+ }
|
|
+ }
|
|
+
|
|
+ throw new SerializationException(obj + " is not of a valid type " + type + " for this node");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected Object serialize(EngineMode item, Predicate<Class<?>> typeSupported) {
|
|
+ return item.getId();
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/serializer/EnumValueSerializer.java b/src/main/java/io/papermc/paper/configuration/serializer/EnumValueSerializer.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..2afb9268447792e3cdb46172b2050dbce066c59a
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/configuration/serializer/EnumValueSerializer.java
|
|
@@ -0,0 +1,50 @@
|
|
+package io.papermc.paper.configuration.serializer;
|
|
+
|
|
+import com.mojang.logging.LogUtils;
|
|
+import io.leangen.geantyref.TypeToken;
|
|
+import org.checkerframework.checker.nullness.qual.Nullable;
|
|
+import org.slf4j.Logger;
|
|
+import org.spongepowered.configurate.serialize.ScalarSerializer;
|
|
+import org.spongepowered.configurate.serialize.SerializationException;
|
|
+import org.spongepowered.configurate.util.EnumLookup;
|
|
+
|
|
+import java.lang.reflect.Type;
|
|
+import java.util.Arrays;
|
|
+import java.util.List;
|
|
+import java.util.function.Predicate;
|
|
+
|
|
+import static io.leangen.geantyref.GenericTypeReflector.erase;
|
|
+
|
|
+/**
|
|
+ * Enum serializer that lists options if fails and accepts `-` as `_`.
|
|
+ */
|
|
+public class EnumValueSerializer extends ScalarSerializer<Enum<?>> {
|
|
+
|
|
+ private static final Logger LOGGER = LogUtils.getLogger();
|
|
+
|
|
+ public EnumValueSerializer() {
|
|
+ super(new TypeToken<Enum<?>>() {});
|
|
+ }
|
|
+
|
|
+ @SuppressWarnings({"rawtypes", "unchecked"})
|
|
+ @Override
|
|
+ public @Nullable Enum<?> deserialize(final Type type, final Object obj) throws SerializationException {
|
|
+ final String enumConstant = obj.toString();
|
|
+ final Class<? extends Enum> typeClass = erase(type).asSubclass(Enum.class);
|
|
+ @Nullable Enum<?> ret = EnumLookup.lookupEnum(typeClass, enumConstant);
|
|
+ if (ret == null) {
|
|
+ ret = EnumLookup.lookupEnum(typeClass, enumConstant.replace("-", "_"));
|
|
+ }
|
|
+ if (ret == null) {
|
|
+ boolean longer = typeClass.getEnumConstants().length > 10;
|
|
+ List<String> options = Arrays.stream(typeClass.getEnumConstants()).limit(10L).map(Enum::name).toList();
|
|
+ LOGGER.error("Invalid enum constant provided, expected one of [" + String.join(", " ,options) + (longer ? ", ..." : "") + "], but got " + enumConstant);
|
|
+ }
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public Object serialize(final Enum<?> item, final Predicate<Class<?>> typeSupported) {
|
|
+ return item.name();
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/serializer/FastutilMapSerializer.java b/src/main/java/io/papermc/paper/configuration/serializer/FastutilMapSerializer.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..f2f362883d1825084c277608c791f82165828ebe
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/configuration/serializer/FastutilMapSerializer.java
|
|
@@ -0,0 +1,69 @@
|
|
+package io.papermc.paper.configuration.serializer;
|
|
+
|
|
+import io.leangen.geantyref.GenericTypeReflector;
|
|
+import io.leangen.geantyref.TypeFactory;
|
|
+import org.checkerframework.checker.nullness.qual.Nullable;
|
|
+import org.spongepowered.configurate.ConfigurationNode;
|
|
+import org.spongepowered.configurate.serialize.SerializationException;
|
|
+import org.spongepowered.configurate.serialize.TypeSerializer;
|
|
+
|
|
+import java.lang.reflect.ParameterizedType;
|
|
+import java.lang.reflect.Type;
|
|
+import java.util.Collections;
|
|
+import java.util.Map;
|
|
+import java.util.function.Function;
|
|
+
|
|
+@SuppressWarnings("rawtypes")
|
|
+public abstract class FastutilMapSerializer<M extends Map<?, ?>> implements TypeSerializer<M> {
|
|
+ private final Function<Map, ? extends M> factory;
|
|
+
|
|
+ protected FastutilMapSerializer(final Function<Map, ? extends M> factory) {
|
|
+ this.factory = factory;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public M deserialize(final Type type, final ConfigurationNode node) throws SerializationException {
|
|
+ @Nullable final Map map = (Map) node.get(this.createBaseMapType((ParameterizedType) type));
|
|
+ return this.factory.apply(map == null ? Collections.emptyMap() : map);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void serialize(final Type type, @Nullable final M obj, final ConfigurationNode node) throws SerializationException {
|
|
+ if (obj == null || obj.isEmpty()) {
|
|
+ node.raw(null);
|
|
+ } else {
|
|
+ final Type baseMapType = this.createBaseMapType((ParameterizedType) type);
|
|
+ node.set(baseMapType, obj);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ protected abstract Type createBaseMapType(final ParameterizedType type);
|
|
+
|
|
+ public static final class SomethingToPrimitive<M extends Map<?, ?>> extends FastutilMapSerializer<M> {
|
|
+ private final Type primitiveType;
|
|
+
|
|
+ public SomethingToPrimitive(final Function<Map, ? extends M> factory, final Type primitiveType) {
|
|
+ super(factory);
|
|
+ this.primitiveType = primitiveType;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected Type createBaseMapType(final ParameterizedType type) {
|
|
+ return TypeFactory.parameterizedClass(Map.class, type.getActualTypeArguments()[0], GenericTypeReflector.box(this.primitiveType));
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public static final class PrimitiveToSomething<M extends Map<?, ?>> extends FastutilMapSerializer<M> {
|
|
+ private final Type primitiveType;
|
|
+
|
|
+ public PrimitiveToSomething(final Function<Map, ? extends M> factory, final Type primitiveType) {
|
|
+ super(factory);
|
|
+ this.primitiveType = primitiveType;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected Type createBaseMapType(final ParameterizedType type) {
|
|
+ return TypeFactory.parameterizedClass(Map.class, GenericTypeReflector.box(this.primitiveType), type.getActualTypeArguments()[0]);
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/serializer/PacketClassSerializer.java b/src/main/java/io/papermc/paper/configuration/serializer/PacketClassSerializer.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..bc065d5cc8975dd189954272116a6bc5bc7f4e28
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/configuration/serializer/PacketClassSerializer.java
|
|
@@ -0,0 +1,86 @@
|
|
+package io.papermc.paper.configuration.serializer;
|
|
+
|
|
+import com.google.common.collect.BiMap;
|
|
+import com.google.common.collect.ImmutableBiMap;
|
|
+import com.mojang.logging.LogUtils;
|
|
+import io.leangen.geantyref.TypeToken;
|
|
+import io.papermc.paper.configuration.serializer.collections.MapSerializer;
|
|
+import io.papermc.paper.util.ObfHelper;
|
|
+import net.minecraft.network.protocol.Packet;
|
|
+import org.checkerframework.checker.nullness.qual.Nullable;
|
|
+import org.slf4j.Logger;
|
|
+import org.spongepowered.configurate.serialize.ScalarSerializer;
|
|
+import org.spongepowered.configurate.serialize.SerializationException;
|
|
+
|
|
+import java.lang.reflect.Type;
|
|
+import java.util.List;
|
|
+import java.util.Map;
|
|
+import java.util.function.Predicate;
|
|
+
|
|
+@SuppressWarnings("Convert2Diamond")
|
|
+public final class PacketClassSerializer extends ScalarSerializer<Class<? extends Packet<?>>> implements MapSerializer.WriteBack {
|
|
+
|
|
+ private static final Logger LOGGER = LogUtils.getLogger();
|
|
+ private static final TypeToken<Class<? extends Packet<?>>> TYPE = new TypeToken<Class<? extends Packet<?>>>() {};
|
|
+ private static final List<String> SUBPACKAGES = List.of("game", "handshake", "login", "status");
|
|
+ private static final BiMap<String, String> MOJANG_TO_OBF;
|
|
+
|
|
+ static {
|
|
+ final ImmutableBiMap.Builder<String, String> builder = ImmutableBiMap.builder();
|
|
+ final @Nullable Map<String, ObfHelper.ClassMapping> classMappingMap = ObfHelper.INSTANCE.mappingsByMojangName();
|
|
+ if (classMappingMap != null) {
|
|
+ classMappingMap.forEach((mojMap, classMapping) -> {
|
|
+ if (mojMap.startsWith("net.minecraft.network.protocol.")) {
|
|
+ builder.put(classMapping.mojangName(), classMapping.obfName());
|
|
+ }
|
|
+ });
|
|
+ }
|
|
+ MOJANG_TO_OBF = builder.build();
|
|
+ }
|
|
+
|
|
+ public PacketClassSerializer() {
|
|
+ super(TYPE);
|
|
+ }
|
|
+
|
|
+ @SuppressWarnings("unchecked")
|
|
+ @Override
|
|
+ public Class<? extends Packet<?>> deserialize(final Type type, final Object obj) throws SerializationException {
|
|
+ @Nullable Class<?> packetClass = null;
|
|
+ for (final String subpackage : SUBPACKAGES) {
|
|
+ final String fullClassName = "net.minecraft.network.protocol." + subpackage + "." + obj;
|
|
+ try {
|
|
+ packetClass = Class.forName(fullClassName);
|
|
+ break;
|
|
+ } catch (final ClassNotFoundException ex) {
|
|
+ final @Nullable String spigotClassName = MOJANG_TO_OBF.get(fullClassName);
|
|
+ if (spigotClassName != null) {
|
|
+ try {
|
|
+ packetClass = Class.forName(spigotClassName);
|
|
+ } catch (final ClassNotFoundException ignore) {}
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ if (packetClass == null || !Packet.class.isAssignableFrom(packetClass)) {
|
|
+ throw new SerializationException("Could not deserialize a packet from " + obj);
|
|
+ }
|
|
+ return (Class<? extends Packet<?>>) packetClass;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected @Nullable Object serialize(final Class<? extends Packet<?>> packetClass, final Predicate<Class<?>> typeSupported) {
|
|
+ final String name = packetClass.getName();
|
|
+ @Nullable String mojName = ObfHelper.INSTANCE.mappingsByMojangName() == null ? name : MOJANG_TO_OBF.inverse().get(name); // if the mappings are null, running on moj-mapped server
|
|
+ if (mojName == null && MOJANG_TO_OBF.containsKey(name)) {
|
|
+ mojName = name;
|
|
+ }
|
|
+ if (mojName != null) {
|
|
+ int pos = mojName.lastIndexOf('.');
|
|
+ if (pos != -1 && pos != mojName.length() - 1) {
|
|
+ return mojName.substring(pos + 1);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ LOGGER.error("Could not serialize {} into a mojang-mapped packet class name", packetClass);
|
|
+ return null;
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/serializer/StringRepresentableSerializer.java b/src/main/java/io/papermc/paper/configuration/serializer/StringRepresentableSerializer.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..add9d16bac9e4570fbdcf8368d7ba03116e97ddf
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/configuration/serializer/StringRepresentableSerializer.java
|
|
@@ -0,0 +1,43 @@
|
|
+package io.papermc.paper.configuration.serializer;
|
|
+
|
|
+import net.minecraft.util.StringRepresentable;
|
|
+import net.minecraft.world.entity.MobCategory;
|
|
+import org.spongepowered.configurate.serialize.ScalarSerializer;
|
|
+import org.spongepowered.configurate.serialize.SerializationException;
|
|
+
|
|
+import java.lang.reflect.Type;
|
|
+import java.util.Collections;
|
|
+import java.util.Map;
|
|
+import java.util.function.Function;
|
|
+import java.util.function.Predicate;
|
|
+
|
|
+public final class StringRepresentableSerializer extends ScalarSerializer<StringRepresentable> {
|
|
+ private static final Map<Type, Function<String, StringRepresentable>> TYPES = Collections.synchronizedMap(Map.ofEntries(
|
|
+ Map.entry(MobCategory.class, s -> {
|
|
+ for (MobCategory value : MobCategory.values()) {
|
|
+ if (value.getSerializedName().equals(s)) {
|
|
+ return value;
|
|
+ }
|
|
+ }
|
|
+ return null;
|
|
+ })
|
|
+ ));
|
|
+
|
|
+ public StringRepresentableSerializer() {
|
|
+ super(StringRepresentable.class);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public StringRepresentable deserialize(Type type, Object obj) throws SerializationException {
|
|
+ Function<String, StringRepresentable> function = TYPES.get(type);
|
|
+ if (function == null) {
|
|
+ throw new SerializationException(type + " isn't registered");
|
|
+ }
|
|
+ return function.apply(obj.toString());
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected Object serialize(StringRepresentable item, Predicate<Class<?>> typeSupported) {
|
|
+ return item.getSerializedName();
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/serializer/TableSerializer.java b/src/main/java/io/papermc/paper/configuration/serializer/TableSerializer.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..346422c5eb791961061cc73b9b827d63bbd67daf
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/configuration/serializer/TableSerializer.java
|
|
@@ -0,0 +1,89 @@
|
|
+package io.papermc.paper.configuration.serializer;
|
|
+
|
|
+import com.google.common.collect.HashBasedTable;
|
|
+import com.google.common.collect.ImmutableTable;
|
|
+import com.google.common.collect.Table;
|
|
+import io.leangen.geantyref.TypeFactory;
|
|
+import org.checkerframework.checker.nullness.qual.Nullable;
|
|
+import org.spongepowered.configurate.BasicConfigurationNode;
|
|
+import org.spongepowered.configurate.ConfigurationNode;
|
|
+import org.spongepowered.configurate.ConfigurationOptions;
|
|
+import org.spongepowered.configurate.serialize.SerializationException;
|
|
+import org.spongepowered.configurate.serialize.TypeSerializer;
|
|
+
|
|
+import java.lang.reflect.ParameterizedType;
|
|
+import java.lang.reflect.Type;
|
|
+import java.util.Map;
|
|
+import java.util.Objects;
|
|
+
|
|
+public class TableSerializer implements TypeSerializer<Table<?, ?, ?>> {
|
|
+ private static final int ROW_TYPE_ARGUMENT_INDEX = 0;
|
|
+ private static final int COLUMN_TYPE_ARGUMENT_INDEX = 1;
|
|
+ private static final int VALUE_TYPE_ARGUMENT_INDEX = 2;
|
|
+
|
|
+ @Override
|
|
+ public Table<?, ?, ?> deserialize(final Type type, final ConfigurationNode node) throws SerializationException {
|
|
+ final Table<?, ?, ?> table = HashBasedTable.create();
|
|
+ if (!node.empty() && node.isMap()) {
|
|
+ this.deserialize0(table, (ParameterizedType) type, node);
|
|
+ }
|
|
+ return table;
|
|
+ }
|
|
+
|
|
+ @SuppressWarnings("unchecked")
|
|
+ private <R, C, V> void deserialize0(final Table<R, C, V> table, final ParameterizedType type, final ConfigurationNode node) throws SerializationException {
|
|
+ final Type rowType = type.getActualTypeArguments()[ROW_TYPE_ARGUMENT_INDEX];
|
|
+ final Type columnType = type.getActualTypeArguments()[COLUMN_TYPE_ARGUMENT_INDEX];
|
|
+ final Type valueType = type.getActualTypeArguments()[VALUE_TYPE_ARGUMENT_INDEX];
|
|
+
|
|
+ final @Nullable TypeSerializer<R> rowKeySerializer = (TypeSerializer<R>) node.options().serializers().get(rowType);
|
|
+ if (rowKeySerializer == null) {
|
|
+ throw new SerializationException("Could not find serializer for table row type " + rowType);
|
|
+ }
|
|
+
|
|
+ final Type mapType = TypeFactory.parameterizedClass(Map.class, columnType, valueType);
|
|
+ final @Nullable TypeSerializer<Map<C, V>> columnValueSerializer = (TypeSerializer<Map<C, V>>) node.options().serializers().get(mapType);
|
|
+ if (columnValueSerializer == null) {
|
|
+ throw new SerializationException("Could not find serializer for table column-value map " + type);
|
|
+ }
|
|
+
|
|
+ final BasicConfigurationNode rowKeyNode = BasicConfigurationNode.root(node.options());
|
|
+
|
|
+ for (final Object key : node.childrenMap().keySet()) {
|
|
+ final R rowKey = rowKeySerializer.deserialize(rowType, rowKeyNode.set(key));
|
|
+ final Map<C, V> map = columnValueSerializer.deserialize(mapType, node.node(rowKeyNode.raw()));
|
|
+ map.forEach((column, value) -> table.put(rowKey, column, value));
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void serialize(final Type type, @Nullable final Table<?, ?, ?> table, final ConfigurationNode node) throws SerializationException {
|
|
+ if (table != null) {
|
|
+ this.serialize0(table, (ParameterizedType) type, node);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @SuppressWarnings({"rawtypes", "unchecked"})
|
|
+ private <R, C, V> void serialize0(final Table<R, C, V> table, final ParameterizedType type, final ConfigurationNode node) throws SerializationException {
|
|
+ final Type rowType = type.getActualTypeArguments()[ROW_TYPE_ARGUMENT_INDEX];
|
|
+ final Type columnType = type.getActualTypeArguments()[COLUMN_TYPE_ARGUMENT_INDEX];
|
|
+ final Type valueType = type.getActualTypeArguments()[VALUE_TYPE_ARGUMENT_INDEX];
|
|
+
|
|
+ final @Nullable TypeSerializer rowKeySerializer = node.options().serializers().get(rowType);
|
|
+ if (rowKeySerializer == null) {
|
|
+ throw new SerializationException("Could not find a serializer for table row type " + rowType);
|
|
+ }
|
|
+
|
|
+ final BasicConfigurationNode rowKeyNode = BasicConfigurationNode.root(node.options());
|
|
+ for (final R key : table.rowKeySet()) {
|
|
+ rowKeySerializer.serialize(rowType, key, rowKeyNode.set(key));
|
|
+ final Object keyObj = Objects.requireNonNull(rowKeyNode.raw());
|
|
+ node.node(keyObj).set(TypeFactory.parameterizedClass(Map.class, columnType, valueType), table.row(key));
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public @Nullable Table<?, ?, ?> emptyValue(Type specificType, ConfigurationOptions options) {
|
|
+ return ImmutableTable.of();
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/serializer/collections/MapSerializer.java b/src/main/java/io/papermc/paper/configuration/serializer/collections/MapSerializer.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..f44d4cb05eab25d79a8ac09b9da981633380c4fc
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/configuration/serializer/collections/MapSerializer.java
|
|
@@ -0,0 +1,162 @@
|
|
+package io.papermc.paper.configuration.serializer.collections;
|
|
+
|
|
+import com.mojang.logging.LogUtils;
|
|
+import io.leangen.geantyref.TypeToken;
|
|
+import org.checkerframework.checker.nullness.qual.Nullable;
|
|
+import org.slf4j.Logger;
|
|
+import org.spongepowered.configurate.BasicConfigurationNode;
|
|
+import org.spongepowered.configurate.ConfigurationNode;
|
|
+import org.spongepowered.configurate.ConfigurationOptions;
|
|
+import org.spongepowered.configurate.NodePath;
|
|
+import org.spongepowered.configurate.serialize.SerializationException;
|
|
+import org.spongepowered.configurate.serialize.TypeSerializer;
|
|
+
|
|
+import java.lang.reflect.ParameterizedType;
|
|
+import java.lang.reflect.Type;
|
|
+import java.util.Collections;
|
|
+import java.util.HashSet;
|
|
+import java.util.LinkedHashMap;
|
|
+import java.util.Map;
|
|
+import java.util.Set;
|
|
+
|
|
+import static java.util.Objects.requireNonNull;
|
|
+
|
|
+/**
|
|
+ * Map serializer that does not throw errors on individual entry serialization failures.
|
|
+ */
|
|
+public class MapSerializer implements TypeSerializer<Map<?, ?>> {
|
|
+
|
|
+ public static final TypeToken<Map<?, ?>> TYPE = new TypeToken<Map<?, ?>>() {};
|
|
+
|
|
+ private static final Logger LOGGER = LogUtils.getLogger();
|
|
+
|
|
+ private final boolean clearInvalids;
|
|
+
|
|
+ public MapSerializer(boolean clearInvalids) {
|
|
+ this.clearInvalids = clearInvalids;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public Map<?, ?> deserialize(Type type, ConfigurationNode node) throws SerializationException {
|
|
+ final Map<Object, Object> map = new LinkedHashMap<>();
|
|
+ if (node.isMap()) {
|
|
+ if (!(type instanceof ParameterizedType parameterizedType)) {
|
|
+ throw new SerializationException(type, "Raw types are not supported for collections");
|
|
+ }
|
|
+ if (parameterizedType.getActualTypeArguments().length != 2) {
|
|
+ throw new SerializationException(type, "Map expected two type arguments!");
|
|
+ }
|
|
+ final Type key = parameterizedType.getActualTypeArguments()[0];
|
|
+ final Type value = parameterizedType.getActualTypeArguments()[1];
|
|
+ final @Nullable TypeSerializer<?> keySerializer = node.options().serializers().get(key);
|
|
+ final @Nullable TypeSerializer<?> valueSerializer = node.options().serializers().get(value);
|
|
+ if (keySerializer == null) {
|
|
+ throw new SerializationException(type, "No type serializer available for key type " + key);
|
|
+ }
|
|
+ if (valueSerializer == null) {
|
|
+ throw new SerializationException(type, "No type serializer available for value type " + value);
|
|
+ }
|
|
+
|
|
+ final BasicConfigurationNode keyNode = BasicConfigurationNode.root(node.options());
|
|
+ final Set<Object> keysToClear = new HashSet<>();
|
|
+ for (Map.Entry<Object, ? extends ConfigurationNode> ent : node.childrenMap().entrySet()) {
|
|
+ final @Nullable Object deserializedKey = deserialize(key, keySerializer, "key", keyNode.set(ent.getKey()), node.path());
|
|
+ final @Nullable Object deserializedValue = deserialize(value, valueSerializer, "value", ent.getValue(), ent.getValue().path());
|
|
+ if (deserializedKey == null || deserializedValue == null) {
|
|
+ continue;
|
|
+ }
|
|
+ if (keySerializer instanceof WriteBack) {
|
|
+ if (serialize(key, keySerializer, deserializedKey, "key", keyNode, node.path()) && !ent.getKey().equals(requireNonNull(keyNode.raw(), "Key must not be null!"))) {
|
|
+ keysToClear.add(ent.getKey());
|
|
+ }
|
|
+ }
|
|
+ map.put(deserializedKey, deserializedValue);
|
|
+ }
|
|
+ if (keySerializer instanceof WriteBack) { // supports cleaning keys which deserialize to the same value
|
|
+ for (Object keyToClear : keysToClear) {
|
|
+ node.node(keyToClear).raw(null);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ return map;
|
|
+ }
|
|
+
|
|
+ private @Nullable Object deserialize(Type type, TypeSerializer<?> serializer, String mapPart, ConfigurationNode node, NodePath path) {
|
|
+ try {
|
|
+ return serializer.deserialize(type, node);
|
|
+ } catch (SerializationException ex) {
|
|
+ ex.initPath(node::path);
|
|
+ LOGGER.error("Could not deserialize {} {} into {} at {}", mapPart, node.raw(), type, path);
|
|
+ }
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void serialize(Type type, @Nullable Map<?, ?> obj, ConfigurationNode node) throws SerializationException {
|
|
+ if (!(type instanceof ParameterizedType parameterizedType)) {
|
|
+ throw new SerializationException(type, "Raw types are not supported for collections");
|
|
+ }
|
|
+ if (parameterizedType.getActualTypeArguments().length != 2) {
|
|
+ throw new SerializationException(type, "Map expected two type arguments!");
|
|
+ }
|
|
+ final Type key = parameterizedType.getActualTypeArguments()[0];
|
|
+ final Type value = parameterizedType.getActualTypeArguments()[1];
|
|
+ final @Nullable TypeSerializer<?> keySerializer = node.options().serializers().get(key);
|
|
+ final @Nullable TypeSerializer<?> valueSerializer = node.options().serializers().get(value);
|
|
+
|
|
+ if (keySerializer == null) {
|
|
+ throw new SerializationException(type, "No type serializer available for key type " + key);
|
|
+ }
|
|
+
|
|
+ if (valueSerializer == null) {
|
|
+ throw new SerializationException(type, "No type serializer available for value type " + value);
|
|
+ }
|
|
+
|
|
+ if (obj == null || obj.isEmpty()) {
|
|
+ node.set(Collections.emptyMap());
|
|
+ } else {
|
|
+ final Set<Object> unvisitedKeys;
|
|
+ if (node.empty()) {
|
|
+ node.raw(Collections.emptyMap());
|
|
+ unvisitedKeys = Collections.emptySet();
|
|
+ } else {
|
|
+ unvisitedKeys = new HashSet<>(node.childrenMap().keySet());
|
|
+ }
|
|
+ final BasicConfigurationNode keyNode = BasicConfigurationNode.root(node.options());
|
|
+ for (Map.Entry<?, ?> ent : obj.entrySet()) {
|
|
+ if (!serialize(key, keySerializer, ent.getKey(), "key", keyNode, node.path())) {
|
|
+ continue;
|
|
+ }
|
|
+ final Object keyObj = requireNonNull(keyNode.raw(), "Key must not be null!");
|
|
+ final ConfigurationNode child = node.node(keyObj);
|
|
+ serialize(value, valueSerializer, ent.getValue(), "value", child, child.path());
|
|
+ unvisitedKeys.remove(keyObj);
|
|
+ }
|
|
+ if (this.clearInvalids) {
|
|
+ for (Object unusedChild : unvisitedKeys) {
|
|
+ node.removeChild(unusedChild);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @SuppressWarnings({"rawtypes", "unchecked"})
|
|
+ private boolean serialize(Type type, TypeSerializer serializer, Object object, String mapPart, ConfigurationNode node, NodePath path) {
|
|
+ try {
|
|
+ serializer.serialize(type, object, node);
|
|
+ return true;
|
|
+ } catch (SerializationException ex) {
|
|
+ ex.initPath(node::path);
|
|
+ LOGGER.error("Could not serialize {} {} from {} at {}", mapPart, object, type, path);
|
|
+ }
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public @Nullable Map<?, ?> emptyValue(Type specificType, ConfigurationOptions options) {
|
|
+ return new LinkedHashMap<>();
|
|
+ }
|
|
+
|
|
+ public interface WriteBack { // marker interface
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/serializer/registry/RegistryEntrySerializer.java b/src/main/java/io/papermc/paper/configuration/serializer/registry/RegistryEntrySerializer.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..0e4e0f1788cf67312cb52bd572784c2f27db71b6
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/configuration/serializer/registry/RegistryEntrySerializer.java
|
|
@@ -0,0 +1,62 @@
|
|
+package io.papermc.paper.configuration.serializer.registry;
|
|
+
|
|
+import io.leangen.geantyref.TypeToken;
|
|
+import net.minecraft.core.Registry;
|
|
+import net.minecraft.resources.ResourceKey;
|
|
+import net.minecraft.resources.ResourceLocation;
|
|
+import net.minecraft.server.MinecraftServer;
|
|
+import org.checkerframework.checker.nullness.qual.Nullable;
|
|
+import org.spongepowered.configurate.serialize.ScalarSerializer;
|
|
+import org.spongepowered.configurate.serialize.SerializationException;
|
|
+
|
|
+import java.lang.reflect.Type;
|
|
+import java.util.function.Predicate;
|
|
+
|
|
+abstract class RegistryEntrySerializer<T, R> extends ScalarSerializer<T> {
|
|
+
|
|
+ private final ResourceKey<? extends Registry<R>> registryKey;
|
|
+ private final boolean omitMinecraftNamespace;
|
|
+
|
|
+ protected RegistryEntrySerializer(TypeToken<T> type, ResourceKey<? extends Registry<R>> registryKey, boolean omitMinecraftNamespace) {
|
|
+ super(type);
|
|
+ this.registryKey = registryKey;
|
|
+ this.omitMinecraftNamespace = omitMinecraftNamespace;
|
|
+ }
|
|
+
|
|
+ protected RegistryEntrySerializer(Class<T> type, ResourceKey<? extends Registry<R>> registryKey, boolean omitMinecraftNamespace) {
|
|
+ super(type);
|
|
+ this.registryKey = registryKey;
|
|
+ this.omitMinecraftNamespace = omitMinecraftNamespace;
|
|
+ }
|
|
+
|
|
+ protected final Registry<R> registry() {
|
|
+ return MinecraftServer.getServer().registryAccess().registryOrThrow(this.registryKey);
|
|
+ }
|
|
+
|
|
+ protected abstract T convertFromResourceKey(ResourceKey<R> key) throws SerializationException;
|
|
+
|
|
+ @Override
|
|
+ public final T deserialize(Type type, Object obj) throws SerializationException {
|
|
+ return this.convertFromResourceKey(this.deserializeKey(obj));
|
|
+ }
|
|
+
|
|
+ protected abstract ResourceKey<R> convertToResourceKey(T value);
|
|
+
|
|
+ @Override
|
|
+ protected final Object serialize(T item, Predicate<Class<?>> typeSupported) {
|
|
+ final ResourceKey<R> key = this.convertToResourceKey(item);
|
|
+ if (this.omitMinecraftNamespace && key.location().getNamespace().equals(ResourceLocation.DEFAULT_NAMESPACE)) {
|
|
+ return key.location().getPath();
|
|
+ } else {
|
|
+ return key.location().toString();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private ResourceKey<R> deserializeKey(final Object input) throws SerializationException {
|
|
+ final @Nullable ResourceLocation key = ResourceLocation.tryParse(input.toString());
|
|
+ if (key == null) {
|
|
+ throw new SerializationException("Could not create a key from " + input);
|
|
+ }
|
|
+ return ResourceKey.create(this.registryKey, key);
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/serializer/registry/RegistryHolderSerializer.java b/src/main/java/io/papermc/paper/configuration/serializer/registry/RegistryHolderSerializer.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..c03c1f277ff8167e8b3e4bfa0f4dfc86834f82f3
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/configuration/serializer/registry/RegistryHolderSerializer.java
|
|
@@ -0,0 +1,34 @@
|
|
+package io.papermc.paper.configuration.serializer.registry;
|
|
+
|
|
+import com.google.common.base.Preconditions;
|
|
+import io.leangen.geantyref.TypeFactory;
|
|
+import io.leangen.geantyref.TypeToken;
|
|
+import net.minecraft.core.Holder;
|
|
+import net.minecraft.core.Registry;
|
|
+import net.minecraft.resources.ResourceKey;
|
|
+import org.spongepowered.configurate.serialize.SerializationException;
|
|
+
|
|
+import java.util.function.Function;
|
|
+
|
|
+public final class RegistryHolderSerializer<T> extends RegistryEntrySerializer<Holder<T>, T> {
|
|
+
|
|
+ @SuppressWarnings("unchecked")
|
|
+ public RegistryHolderSerializer(TypeToken<T> typeToken, ResourceKey<? extends Registry<T>> registryKey, boolean omitMinecraftNamespace) {
|
|
+ super((TypeToken<Holder<T>>) TypeToken.get(TypeFactory.parameterizedClass(Holder.class, typeToken.getType())), registryKey, omitMinecraftNamespace);
|
|
+ }
|
|
+
|
|
+ public RegistryHolderSerializer(Class<T> type, ResourceKey<? extends Registry<T>> registryKey, boolean omitMinecraftNamespace) {
|
|
+ this(TypeToken.get(type), registryKey, omitMinecraftNamespace);
|
|
+ Preconditions.checkArgument(type.getTypeParameters().length == 0, "%s must have 0 type parameters", type);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected Holder<T> convertFromResourceKey(ResourceKey<T> key) throws SerializationException {
|
|
+ return this.registry().getHolder(key).orElseThrow(() -> new SerializationException("Missing holder in " + this.registry().key() + " with key " + key));
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected ResourceKey<T> convertToResourceKey(Holder<T> value) {
|
|
+ return value.unwrap().map(Function.identity(), r -> this.registry().getResourceKey(r).orElseThrow());
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/serializer/registry/RegistryValueSerializer.java b/src/main/java/io/papermc/paper/configuration/serializer/registry/RegistryValueSerializer.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..10d3dd361cd26dc849ebd53c1235aa8e4f7af04d
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/configuration/serializer/registry/RegistryValueSerializer.java
|
|
@@ -0,0 +1,34 @@
|
|
+package io.papermc.paper.configuration.serializer.registry;
|
|
+
|
|
+import io.leangen.geantyref.TypeToken;
|
|
+import net.minecraft.core.Registry;
|
|
+import net.minecraft.resources.ResourceKey;
|
|
+import org.spongepowered.configurate.serialize.SerializationException;
|
|
+
|
|
+/**
|
|
+ * Use {@link RegistryHolderSerializer} for datapack-configurable things.
|
|
+ */
|
|
+public final class RegistryValueSerializer<T> extends RegistryEntrySerializer<T, T> {
|
|
+
|
|
+ public RegistryValueSerializer(TypeToken<T> type, ResourceKey<? extends Registry<T>> registryKey, boolean omitMinecraftNamespace) {
|
|
+ super(type, registryKey, omitMinecraftNamespace);
|
|
+ }
|
|
+
|
|
+ public RegistryValueSerializer(Class<T> type, ResourceKey<? extends Registry<T>> registryKey, boolean omitMinecraftNamespace) {
|
|
+ super(type, registryKey, omitMinecraftNamespace);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected T convertFromResourceKey(ResourceKey<T> key) throws SerializationException {
|
|
+ final T value = this.registry().get(key);
|
|
+ if (value == null) {
|
|
+ throw new SerializationException("Missing value in " + this.registry() + " with key " + key.location());
|
|
+ }
|
|
+ return value;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected ResourceKey<T> convertToResourceKey(T value) {
|
|
+ return this.registry().getResourceKey(value).orElseThrow();
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/transformation/Transformations.java b/src/main/java/io/papermc/paper/configuration/transformation/Transformations.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..0300fb1e09d41465e4a50bfdc987b9571289d399
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/configuration/transformation/Transformations.java
|
|
@@ -0,0 +1,35 @@
|
|
+package io.papermc.paper.configuration.transformation;
|
|
+
|
|
+import io.papermc.paper.configuration.Configurations;
|
|
+import org.spongepowered.configurate.ConfigurationNode;
|
|
+import org.spongepowered.configurate.NodePath;
|
|
+import org.spongepowered.configurate.transformation.ConfigurationTransformation;
|
|
+
|
|
+import static org.spongepowered.configurate.NodePath.path;
|
|
+
|
|
+public final class Transformations {
|
|
+ private Transformations() {
|
|
+ }
|
|
+
|
|
+ public static void moveFromRoot(final ConfigurationTransformation.Builder builder, final String key, final String... parents) {
|
|
+ moveFromRootAndRename(builder, key, key, parents);
|
|
+ }
|
|
+
|
|
+ public static void moveFromRootAndRename(final ConfigurationTransformation.Builder builder, final String oldKey, final String newKey, final String... parents) {
|
|
+ moveFromRootAndRename(builder, path(oldKey), newKey, parents);
|
|
+ }
|
|
+
|
|
+ public static void moveFromRootAndRename(final ConfigurationTransformation.Builder builder, final NodePath oldKey, final String newKey, final String... parents) {
|
|
+ builder.addAction(oldKey, (path, value) -> {
|
|
+ final Object[] newPath = new Object[parents.length + 1];
|
|
+ newPath[parents.length] = newKey;
|
|
+ System.arraycopy(parents, 0, newPath, 0, parents.length);
|
|
+ return newPath;
|
|
+ });
|
|
+ }
|
|
+
|
|
+ @FunctionalInterface
|
|
+ public interface DefaultsAware {
|
|
+ void apply(final ConfigurationTransformation.Builder builder, final Configurations.ContextMap contextMap, final ConfigurationNode defaultsNode);
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/transformation/global/LegacyPaperConfig.java b/src/main/java/io/papermc/paper/configuration/transformation/global/LegacyPaperConfig.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..d21335930652ffced22f6fd19ab1a4f9ad599db8
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/configuration/transformation/global/LegacyPaperConfig.java
|
|
@@ -0,0 +1,222 @@
|
|
+package io.papermc.paper.configuration.transformation.global;
|
|
+
|
|
+import com.mojang.logging.LogUtils;
|
|
+import io.papermc.paper.configuration.Configuration;
|
|
+import net.kyori.adventure.text.Component;
|
|
+import net.kyori.adventure.text.format.NamedTextColor;
|
|
+import net.kyori.adventure.text.minimessage.MiniMessage;
|
|
+import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
|
+import net.minecraft.network.protocol.game.ServerboundPlaceRecipePacket;
|
|
+import org.bukkit.ChatColor;
|
|
+import org.bukkit.configuration.file.YamlConfiguration;
|
|
+import org.checkerframework.checker.nullness.qual.Nullable;
|
|
+import org.slf4j.Logger;
|
|
+import org.spongepowered.configurate.ConfigurationNode;
|
|
+import org.spongepowered.configurate.transformation.ConfigurationTransformation;
|
|
+import org.spongepowered.configurate.transformation.TransformAction;
|
|
+
|
|
+import java.util.function.Predicate;
|
|
+
|
|
+import static org.spongepowered.configurate.NodePath.path;
|
|
+
|
|
+public final class LegacyPaperConfig {
|
|
+ private static final Logger LOGGER = LogUtils.getLogger();
|
|
+
|
|
+ private LegacyPaperConfig() {
|
|
+ }
|
|
+
|
|
+ public static ConfigurationTransformation transformation(final YamlConfiguration spigotConfiguration) {
|
|
+ return ConfigurationTransformation.chain(versioned(), notVersioned(spigotConfiguration));
|
|
+ }
|
|
+
|
|
+ // Represents version transforms lifted directly from the old PaperConfig class
|
|
+ // must be run BEFORE the "settings" flatten
|
|
+ private static ConfigurationTransformation.Versioned versioned() {
|
|
+ return ConfigurationTransformation.versionedBuilder()
|
|
+ .versionKey(Configuration.LEGACY_CONFIG_VERSION_FIELD)
|
|
+ .addVersion(11, ConfigurationTransformation.builder().addAction(path("settings", "play-in-use-item-spam-threshold"), TransformAction.rename("incoming-packet-spam-threshold")).build())
|
|
+ .addVersion(14, ConfigurationTransformation.builder().addAction(path("settings", "spam-limiter", "tab-spam-increment"), (path, value) -> {
|
|
+ if (value.getInt() == 10) {
|
|
+ value.set(2);
|
|
+ }
|
|
+ return null;
|
|
+ }).build())
|
|
+ .addVersion(15, ConfigurationTransformation.builder().addAction(path("settings"), (path, value) -> {
|
|
+ value.node("async-chunks", "threads").set(-1);
|
|
+ return null;
|
|
+ }).build())
|
|
+ .addVersion(21, ConfigurationTransformation.builder().addAction(path("use-display-name-in-quit-message"), (path, value) -> new Object[]{"settings", "use-display-name-in-quit-message"}).build())
|
|
+ .addVersion(23, ConfigurationTransformation.builder().addAction(path("settings", "chunk-loading", "global-max-chunk-load-rate"), (path, value) -> {
|
|
+ if (value.getDouble() == 300.0) {
|
|
+ value.set(-1.0);
|
|
+ }
|
|
+ return null;
|
|
+ }).build())
|
|
+ .addVersion(25, ConfigurationTransformation.builder().addAction(path("settings", "chunk-loading", "player-max-concurrent-loads"), (path, value) -> {
|
|
+ if (value.getDouble() == 4.0) {
|
|
+ value.set(20.0);
|
|
+ }
|
|
+ return null;
|
|
+ }).build())
|
|
+ .build();
|
|
+ }
|
|
+
|
|
+ // other non-versioned transforms found in PaperConfig
|
|
+ // must be run BEFORE the "settings" flatten
|
|
+ private static ConfigurationTransformation notVersioned(final YamlConfiguration spigotConfiguration) {
|
|
+ return ConfigurationTransformation.builder()
|
|
+ .addAction(path("settings"), (path, value) -> {
|
|
+ final ConfigurationNode node = value.node("async-chunks");
|
|
+ if (node.hasChild("load-threads")) {
|
|
+ if (!node.hasChild("threads")) {
|
|
+ node.node("threads").set(node.node("load-threads").getInt());
|
|
+ }
|
|
+ node.removeChild("load-threads");
|
|
+ }
|
|
+ node.removeChild("generation");
|
|
+ node.removeChild("enabled");
|
|
+ node.removeChild("thread-per-world-generation");
|
|
+ return null;
|
|
+ })
|
|
+ .addAction(path("allow-perm-block-break-exploits"), (path, value) -> new Object[]{"settings", "unsupported-settings", "allow-permanent-block-break-exploits"})
|
|
+ .addAction(path("settings", "unsupported-settings", "allow-tnt-duplication"), TransformAction.rename("allow-piston-duplication"))
|
|
+ .addAction(path("settings", "save-player-data"), (path, value) -> {
|
|
+ final @Nullable Object val = value.raw();
|
|
+ if (val instanceof Boolean bool) {
|
|
+ spigotConfiguration.set("players.disable-saving", !bool);
|
|
+ }
|
|
+ value.raw(null);
|
|
+ return null;
|
|
+ })
|
|
+ .addAction(path("settings", "log-named-entity-deaths"), (path, value) -> {
|
|
+ final @Nullable Object val = value.raw();
|
|
+ if (val instanceof Boolean bool && !bool) {
|
|
+ spigotConfiguration.set("settings.log-named-deaths", false);
|
|
+ }
|
|
+ value.raw(null);
|
|
+ return null;
|
|
+ })
|
|
+ .build();
|
|
+ }
|
|
+
|
|
+ // transforms to new format with configurate
|
|
+ // must be run AFTER the "settings" flatten
|
|
+ public static ConfigurationTransformation toNewFormat() {
|
|
+ return ConfigurationTransformation.chain(
|
|
+ ConfigurationTransformation.versionedBuilder().versionKey(Configuration.LEGACY_CONFIG_VERSION_FIELD).addVersion(Configuration.FINAL_LEGACY_VERSION + 1, newFormatTransformation()).build(),
|
|
+ ConfigurationTransformation.builder().addAction(path(Configuration.LEGACY_CONFIG_VERSION_FIELD), TransformAction.rename(Configuration.VERSION_FIELD)).build() // rename to _version to place at the top
|
|
+ );
|
|
+ }
|
|
+
|
|
+ private static ConfigurationTransformation newFormatTransformation() {
|
|
+ final ConfigurationTransformation.Builder builder = ConfigurationTransformation.builder()
|
|
+ .addAction(path("verbose"), TransformAction.remove()) // not needed
|
|
+ .addAction(path("unsupported-settings", "allow-headless-pistons-readme"), TransformAction.remove())
|
|
+ .addAction(path("unsupported-settings", "allow-permanent-block-break-exploits-readme"), TransformAction.remove())
|
|
+ .addAction(path("unsupported-settings", "allow-piston-duplication-readme"), TransformAction.remove())
|
|
+ .addAction(path("packet-limiter", "limits", "all"), (path, value) -> new Object[]{"packet-limiter", "all-packets"})
|
|
+ .addAction(path("packet-limiter", "limits"), (path, value) -> new Object[]{"packet-limiter", "overrides"})
|
|
+ .addAction(path("packet-limiter", "overrides", ConfigurationTransformation.WILDCARD_OBJECT), (path, value) -> {
|
|
+ final @Nullable Object keyValue = value.key();
|
|
+ if (keyValue != null && keyValue.toString().equals("PacketPlayInAutoRecipe")) { // add special cast to handle the default for moj-mapped servers that upgrade the config
|
|
+ return path.with(path.size() - 1, ServerboundPlaceRecipePacket.class.getSimpleName()).array();
|
|
+ }
|
|
+ return null;
|
|
+ }).addAction(path("loggers"), TransformAction.rename("logging"));
|
|
+
|
|
+ moveFromRootAndRename(builder, "incoming-packet-spam-threshold", "incoming-packet-threshold", "spam-limiter");
|
|
+
|
|
+ moveFromRoot(builder, "save-empty-scoreboard-teams", "scoreboards");
|
|
+ moveFromRoot(builder, "track-plugin-scoreboards", "scoreboards");
|
|
+
|
|
+ moveFromRoot(builder, "suggest-player-names-when-null-tab-completions", "commands");
|
|
+ moveFromRoot(builder, "time-command-affects-all-worlds", "commands");
|
|
+ moveFromRoot(builder, "fix-target-selector-tag-completion", "commands");
|
|
+
|
|
+ moveFromRoot(builder, "log-player-ip-addresses", "loggers");
|
|
+
|
|
+ moveFromRoot(builder, "use-display-name-in-quit-message", "messages");
|
|
+
|
|
+ moveFromRootAndRename(builder, "console-has-all-permissions", "has-all-permissions", "console");
|
|
+
|
|
+ moveFromRootAndRename(builder, "bungee-online-mode", "online-mode", "proxies", "bungee-cord");
|
|
+ moveFromRootAndRename(builder, "velocity-support", "velocity", "proxies");
|
|
+
|
|
+ moveFromRoot(builder, "book-size", "item-validation");
|
|
+ moveFromRoot(builder, "resolve-selectors-in-books", "item-validation");
|
|
+
|
|
+ moveFromRoot(builder, "enable-player-collisions", "collisions");
|
|
+ moveFromRoot(builder, "send-full-pos-for-hard-colliding-entities", "collisions");
|
|
+
|
|
+ moveFromRootAndRename(builder, "player-auto-save-rate", "rate", "player-auto-save");
|
|
+ moveFromRootAndRename(builder, "max-player-auto-save-per-tick", "max-per-tick", "player-auto-save");
|
|
+
|
|
+ moveFromRootToMisc(builder, "max-joins-per-tick");
|
|
+ moveFromRootToMisc(builder, "fix-entity-position-desync");
|
|
+ moveFromRootToMisc(builder, "load-permissions-yml-before-plugins");
|
|
+ moveFromRootToMisc(builder, "region-file-cache-size");
|
|
+ moveFromRootToMisc(builder, "use-alternative-luck-formula");
|
|
+ moveFromRootToMisc(builder, "lag-compensate-block-breaking");
|
|
+ moveFromRootToMisc(builder, "use-dimension-type-for-custom-spawners");
|
|
+
|
|
+ moveFromRoot(builder, "proxy-protocol", "proxies");
|
|
+
|
|
+ miniMessageWithTranslatable(builder, String::isBlank, "multiplayer.disconnect.authservers_down", "messages", "kick", "authentication-servers-down");
|
|
+ miniMessageWithTranslatable(builder, Predicate.isEqual("Flying is not enabled on this server"), "multiplayer.disconnect.flying", "messages", "kick", "flying-player");
|
|
+ miniMessageWithTranslatable(builder, Predicate.isEqual("Flying is not enabled on this server"), "multiplayer.disconnect.flying", "messages", "kick", "flying-vehicle");
|
|
+ miniMessage(builder, "messages", "kick", "connection-throttle");
|
|
+ miniMessage(builder, "messages", "no-permission");
|
|
+ miniMessageWithTranslatable(builder, Predicate.isEqual("&cSent too many packets"), Component.translatable("disconnect.exceeded_packet_rate", NamedTextColor.RED), "packet-limiter", "kick-message");
|
|
+
|
|
+ return builder.build();
|
|
+ }
|
|
+
|
|
+ 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) -> {
|
|
+ final @Nullable Object val = value.raw();
|
|
+ if (val != null) {
|
|
+ final String strVal = val.toString();
|
|
+ if (!englishCheck.test(strVal)) {
|
|
+ value.set(miniMessage(strVal));
|
|
+ return null;
|
|
+ }
|
|
+ }
|
|
+ value.set(MiniMessage.miniMessage().serialize(component));
|
|
+ return null;
|
|
+ });
|
|
+ }
|
|
+
|
|
+ private static void miniMessage(final ConfigurationTransformation.Builder builder, final String... strPath) {
|
|
+ builder.addAction(path((Object[]) strPath), (path, value) -> {
|
|
+ final @Nullable Object val = value.raw();
|
|
+ if (val != null) {
|
|
+ value.set(miniMessage(val.toString()));
|
|
+ }
|
|
+ return null;
|
|
+ });
|
|
+ }
|
|
+
|
|
+ private static String miniMessage(final String input) {
|
|
+ return MiniMessage.miniMessage().serialize(LegacyComponentSerializer.legacySection().deserialize(ChatColor.translateAlternateColorCodes('&', input)));
|
|
+ }
|
|
+
|
|
+ private static void moveFromRootToMisc(final ConfigurationTransformation.Builder builder, final String key) {
|
|
+ moveFromRoot(builder, key, "misc");
|
|
+ }
|
|
+
|
|
+ private static void moveFromRoot(final ConfigurationTransformation.Builder builder, final String key, final String... parents) {
|
|
+ moveFromRootAndRename(builder, key, key, parents);
|
|
+ }
|
|
+
|
|
+ private static void moveFromRootAndRename(final ConfigurationTransformation.Builder builder, final String oldKey, final String newKey, final String... parents) {
|
|
+ builder.addAction(path(oldKey), (path, value) -> {
|
|
+ final Object[] newPath = new Object[parents.length + 1];
|
|
+ newPath[parents.length] = newKey;
|
|
+ System.arraycopy(parents, 0, newPath, 0, parents.length);
|
|
+ return newPath;
|
|
+ });
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/transformation/world/FeatureSeedsGeneration.java b/src/main/java/io/papermc/paper/configuration/transformation/world/FeatureSeedsGeneration.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..75f612b04f872d0d014fdc40b07c15116857587b
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/configuration/transformation/world/FeatureSeedsGeneration.java
|
|
@@ -0,0 +1,71 @@
|
|
+package io.papermc.paper.configuration.transformation.world;
|
|
+
|
|
+import com.mojang.logging.LogUtils;
|
|
+import io.leangen.geantyref.TypeToken;
|
|
+import io.papermc.paper.configuration.Configurations;
|
|
+import it.unimi.dsi.fastutil.objects.Reference2LongMap;
|
|
+import it.unimi.dsi.fastutil.objects.Reference2LongOpenHashMap;
|
|
+import net.minecraft.core.Holder;
|
|
+import net.minecraft.core.Registry;
|
|
+import net.minecraft.resources.ResourceLocation;
|
|
+import net.minecraft.server.MinecraftServer;
|
|
+import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
|
|
+import org.checkerframework.checker.nullness.qual.Nullable;
|
|
+import org.slf4j.Logger;
|
|
+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 java.security.SecureRandom;
|
|
+import java.util.Objects;
|
|
+import java.util.Random;
|
|
+import java.util.concurrent.atomic.AtomicInteger;
|
|
+
|
|
+import static org.spongepowered.configurate.NodePath.path;
|
|
+
|
|
+public class FeatureSeedsGeneration implements TransformAction {
|
|
+
|
|
+ public static final String FEATURE_SEEDS_KEY = "feature-seeds";
|
|
+ public static final String GENERATE_KEY = "generate-random-seeds-for-all";
|
|
+ public static final String FEATURES_KEY = "features";
|
|
+
|
|
+ private static final Logger LOGGER = LogUtils.getLogger();
|
|
+
|
|
+ private final ResourceLocation worldKey;
|
|
+
|
|
+ private FeatureSeedsGeneration(ResourceLocation worldKey) {
|
|
+ this.worldKey = worldKey;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public Object @Nullable [] visitPath(NodePath path, ConfigurationNode value) throws ConfigurateException {
|
|
+ ConfigurationNode featureNode = value.node(FEATURE_SEEDS_KEY, FEATURES_KEY);
|
|
+ final Reference2LongMap<Holder<ConfiguredFeature<?, ?>>> features = Objects.requireNonNullElseGet(featureNode.get(new TypeToken<Reference2LongMap<Holder<ConfiguredFeature<?, ?>>>>() {}), Reference2LongOpenHashMap::new);
|
|
+ final Random random = new SecureRandom();
|
|
+ AtomicInteger counter = new AtomicInteger(0);
|
|
+ MinecraftServer.getServer().registryAccess().registryOrThrow(Registry.CONFIGURED_FEATURE_REGISTRY).holders().forEach(holder -> {
|
|
+ if (features.containsKey(holder)) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ final long seed = random.nextLong();
|
|
+ features.put(holder, seed);
|
|
+ counter.incrementAndGet();
|
|
+ });
|
|
+ if (counter.get() > 0) {
|
|
+ LOGGER.info("Generated {} random feature seeds for {}", counter.get(), this.worldKey);
|
|
+ featureNode.raw(null);
|
|
+ featureNode.set(new TypeToken<Reference2LongMap<Holder<ConfiguredFeature<?, ?>>>>() {}, features);
|
|
+ }
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+
|
|
+ public static void apply(final ConfigurationTransformation.Builder builder, final Configurations.ContextMap contextMap, final ConfigurationNode defaultsNode) {
|
|
+ if (!contextMap.isDefaultWorldContext() && defaultsNode.node(FEATURE_SEEDS_KEY, GENERATE_KEY).getBoolean(false)) {
|
|
+ builder.addAction(path(), new FeatureSeedsGeneration(contextMap.require(Configurations.WORLD_KEY)));
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/transformation/world/LegacyPaperWorldConfig.java b/src/main/java/io/papermc/paper/configuration/transformation/world/LegacyPaperWorldConfig.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..6af307481a6752529d87869760945cb140d05bed
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/configuration/transformation/world/LegacyPaperWorldConfig.java
|
|
@@ -0,0 +1,321 @@
|
|
+package io.papermc.paper.configuration.transformation.world;
|
|
+
|
|
+import io.papermc.paper.configuration.Configuration;
|
|
+import io.papermc.paper.configuration.WorldConfiguration;
|
|
+import net.minecraft.core.Holder;
|
|
+import net.minecraft.core.Registry;
|
|
+import net.minecraft.resources.ResourceKey;
|
|
+import net.minecraft.resources.ResourceLocation;
|
|
+import net.minecraft.world.entity.MobCategory;
|
|
+import net.minecraft.world.item.Item;
|
|
+import org.bukkit.Material;
|
|
+import org.checkerframework.checker.nullness.qual.Nullable;
|
|
+import org.spongepowered.configurate.transformation.ConfigurationTransformation;
|
|
+import org.spongepowered.configurate.transformation.TransformAction;
|
|
+
|
|
+import java.util.HashMap;
|
|
+import java.util.List;
|
|
+import java.util.Locale;
|
|
+import java.util.Map;
|
|
+import java.util.Optional;
|
|
+
|
|
+import static io.papermc.paper.configuration.transformation.Transformations.moveFromRoot;
|
|
+import static io.papermc.paper.configuration.transformation.Transformations.moveFromRootAndRename;
|
|
+import static org.spongepowered.configurate.NodePath.path;
|
|
+
|
|
+public final class LegacyPaperWorldConfig {
|
|
+
|
|
+ private LegacyPaperWorldConfig() {
|
|
+ }
|
|
+
|
|
+ public static ConfigurationTransformation transformation() {
|
|
+ return ConfigurationTransformation.chain(versioned(), notVersioned());
|
|
+ }
|
|
+
|
|
+ private static ConfigurationTransformation.Versioned versioned() {
|
|
+ return ConfigurationTransformation.versionedBuilder().versionKey(Configuration.LEGACY_CONFIG_VERSION_FIELD)
|
|
+ .addVersion(13, ConfigurationTransformation.builder().addAction(path("enable-old-tnt-cannon-behaviors"), TransformAction.rename("prevent-tnt-from-moving-in-water")).build())
|
|
+ .addVersion(16, ConfigurationTransformation.builder().addAction(path("use-chunk-inhabited-timer"), (path, value) -> {
|
|
+ if (!value.getBoolean(true)) {
|
|
+ value.raw(0);
|
|
+ } else {
|
|
+ value.raw(-1);
|
|
+ }
|
|
+ final Object[] newPath = path.array();
|
|
+ newPath[newPath.length - 1] = "fixed-chunk-inhabited-time";
|
|
+ return newPath;
|
|
+ }).build())
|
|
+ .addVersion(18, ConfigurationTransformation.builder().addAction(path("nether-ceiling-void-damage"), (path, value) -> {
|
|
+ if (value.getBoolean(false)) {
|
|
+ value.raw(128);
|
|
+ } else {
|
|
+ value.raw(0);
|
|
+ }
|
|
+ final Object[] newPath = path.array();
|
|
+ newPath[newPath.length - 1] = "nether-ceiling-void-damage-height";
|
|
+ return newPath;
|
|
+ }).build())
|
|
+ .addVersion(19, ConfigurationTransformation.builder()
|
|
+ .addAction(path("anti-xray", "hidden-blocks"), (path, value) -> {
|
|
+ @Nullable final List<String> hiddenBlocks = value.getList(String.class);
|
|
+ if (hiddenBlocks != null) {
|
|
+ hiddenBlocks.remove("lit_redstone_ore");
|
|
+ }
|
|
+ return null;
|
|
+ })
|
|
+ .addAction(path("anti-xray", "replacement-blocks"), (path, value) -> {
|
|
+ @Nullable final List<String> replacementBlocks = value.getList(String.class);
|
|
+ if (replacementBlocks != null) {
|
|
+ final int index = replacementBlocks.indexOf("planks");
|
|
+ if (index != -1) {
|
|
+ replacementBlocks.set(index, "oak_planks");
|
|
+ }
|
|
+ }
|
|
+ value.raw(replacementBlocks);
|
|
+ return null;
|
|
+ }).build())
|
|
+ .addVersion(20, ConfigurationTransformation.builder().addAction(path("baby-zombie-movement-speed"), TransformAction.rename("baby-zombie-movement-modifier")).build())
|
|
+ .addVersion(22, ConfigurationTransformation.builder().addAction(path("per-player-mob-spawns"), (path, value) -> {
|
|
+ value.raw(true);
|
|
+ return null;
|
|
+ }).build())
|
|
+ .addVersion(24,
|
|
+ ConfigurationTransformation.builder()
|
|
+ .addAction(path("spawn-limits", "monsters"), TransformAction.rename("monster"))
|
|
+ .addAction(path("spawn-limits", "animals"), TransformAction.rename("creature"))
|
|
+ .addAction(path("spawn-limits", "water-animals"), TransformAction.rename("water_creature"))
|
|
+ .addAction(path("spawn-limits", "water-ambient"), TransformAction.rename("water_ambient"))
|
|
+ .build(),
|
|
+ ConfigurationTransformation.builder().addAction(path("despawn-ranges"), (path, value) -> {
|
|
+ final int softDistance = value.node("soft").getInt(32);
|
|
+ final int hardDistance = value.node("hard").getInt(128);
|
|
+ value.node("soft").raw(null);
|
|
+ value.node("hard").raw(null);
|
|
+ for (final MobCategory category : MobCategory.values()) {
|
|
+ if (softDistance != 32) {
|
|
+ value.node(category.getName(), "soft").raw(softDistance);
|
|
+ }
|
|
+ if (hardDistance != 128) {
|
|
+ value.node(category.getName(), "hard").raw(hardDistance);
|
|
+ }
|
|
+ }
|
|
+ return null;
|
|
+ }).build()
|
|
+ )
|
|
+ .addVersion(26, ConfigurationTransformation.builder().addAction(path("alt-item-despawn-rate", "items", ConfigurationTransformation.WILDCARD_OBJECT), (path, value) -> {
|
|
+ String itemName = path.get(path.size() - 1).toString();
|
|
+ final Optional<Holder<Item>> item = Registry.ITEM.getHolder(ResourceKey.create(Registry.ITEM_REGISTRY, new ResourceLocation(itemName.toLowerCase(Locale.ENGLISH))));
|
|
+ if (item.isEmpty()) {
|
|
+ itemName = Material.valueOf(itemName).getKey().getKey();
|
|
+ }
|
|
+ final Object[] newPath = path.array();
|
|
+ newPath[newPath.length - 1] = itemName;
|
|
+ return newPath;
|
|
+ }).build())
|
|
+ .addVersion(27, ConfigurationTransformation.builder().addAction(path("use-faster-eigencraft-redstone"), (path, value) -> {
|
|
+ final WorldConfiguration.Misc.RedstoneImplementation redstoneImplementation = value.getBoolean(false) ? WorldConfiguration.Misc.RedstoneImplementation.EIGENCRAFT : WorldConfiguration.Misc.RedstoneImplementation.VANILLA;
|
|
+ value.set(redstoneImplementation);
|
|
+ final Object[] newPath = path.array();
|
|
+ newPath[newPath.length - 1] = "redstone-implementation";
|
|
+ return newPath;
|
|
+ }).build())
|
|
+ .build();
|
|
+ }
|
|
+
|
|
+ // other transformations found in PaperWorldConfig that aren't versioned
|
|
+ private static ConfigurationTransformation notVersioned() {
|
|
+ return ConfigurationTransformation.builder()
|
|
+ .addAction(path("treasure-maps-return-already-discovered"), (path, value) -> {
|
|
+ boolean prevValue = value.getBoolean(false);
|
|
+ value.node("villager-trade").set(prevValue);
|
|
+ value.node("loot-tables").set(prevValue);
|
|
+ return path.with(path.size() - 1, "treasure-maps-find-already-discovered").array();
|
|
+ })
|
|
+ .addAction(path("alt-item-despawn-rate", "items"), (path, value) -> {
|
|
+ if (value.isMap()) {
|
|
+ Map<String, Integer> rebuild = new HashMap<>();
|
|
+ value.childrenMap().forEach((key, node) -> {
|
|
+ String itemName = key.toString();
|
|
+ final Optional<Holder<Item>> itemHolder = Registry.ITEM.getHolder(ResourceKey.create(Registry.ITEM_REGISTRY, new ResourceLocation(itemName.toLowerCase(Locale.ENGLISH))));
|
|
+ final @Nullable String item;
|
|
+ if (itemHolder.isEmpty()) {
|
|
+ final @Nullable Material bukkitMat = Material.matchMaterial(itemName);
|
|
+ item = bukkitMat != null ? bukkitMat.getKey().getKey() : null;
|
|
+ } else {
|
|
+ item = itemHolder.get().unwrapKey().orElseThrow().location().getPath();
|
|
+ }
|
|
+ if (item != null) {
|
|
+ rebuild.put(item, node.getInt());
|
|
+ }
|
|
+ });
|
|
+ value.set(rebuild);
|
|
+ }
|
|
+ return null;
|
|
+ })
|
|
+ .build();
|
|
+ }
|
|
+
|
|
+ public static ConfigurationTransformation toNewFormat() {
|
|
+ return ConfigurationTransformation.chain(ConfigurationTransformation.versionedBuilder().versionKey(Configuration.LEGACY_CONFIG_VERSION_FIELD).addVersion(Configuration.FINAL_LEGACY_VERSION + 1, newFormatTransformation()).build(), ConfigurationTransformation.builder().addAction(path(Configuration.LEGACY_CONFIG_VERSION_FIELD), TransformAction.rename(Configuration.VERSION_FIELD)).build());
|
|
+ }
|
|
+
|
|
+ private static ConfigurationTransformation newFormatTransformation() {
|
|
+ final ConfigurationTransformation.Builder builder = ConfigurationTransformation.builder()
|
|
+ .addAction(path("verbose"), TransformAction.remove()); // not needed
|
|
+
|
|
+ moveFromRoot(builder, "anti-xray", "anticheat");
|
|
+
|
|
+ moveFromRootAndRename(builder, "armor-stands-do-collision-entity-lookups", "do-collision-entity-lookups", "entities", "armor-stands");
|
|
+ moveFromRootAndRename(builder, "armor-stands-tick", "tick", "entities", "armor-stands");
|
|
+
|
|
+ moveFromRoot(builder, "auto-save-interval", "chunks");
|
|
+ moveFromRoot(builder, "delay-chunk-unloads-by", "chunks");
|
|
+ moveFromRoot(builder, "entity-per-chunk-save-limit", "chunks");
|
|
+ moveFromRoot(builder, "fixed-chunk-inhabited-time", "chunks");
|
|
+ moveFromRoot(builder, "max-auto-save-chunks-per-tick", "chunks");
|
|
+ moveFromRoot(builder, "prevent-moving-into-unloaded-chunks", "chunks");
|
|
+
|
|
+ moveFromRoot(builder, "entities-target-with-follow-range", "entities");
|
|
+ moveFromRoot(builder, "mob-effects", "entities");
|
|
+
|
|
+ moveFromRoot(builder, "filter-nbt-data-from-spawn-eggs-and-related", "entities", "spawning");
|
|
+ moveFromGameMechanics(builder, "disable-mob-spawner-spawn-egg-transformation", "entities", "spawning");
|
|
+ moveFromRoot(builder, "per-player-mob-spawns", "entities", "spawning");
|
|
+ moveFromGameMechanics(builder, "scan-for-legacy-ender-dragon", "entities", "spawning");
|
|
+ moveFromRoot(builder, "spawn-limits", "entities", "spawning");
|
|
+ moveFromRoot(builder, "despawn-ranges", "entities", "spawning");
|
|
+ moveFromRoot(builder, "wateranimal-spawn-height", "entities", "spawning");
|
|
+ builder.addAction(path("slime-spawn-height", "swamp-biome"), TransformAction.rename("surface-biome"));
|
|
+ moveFromRoot(builder, "slime-spawn-height", "entities", "spawning");
|
|
+ moveFromRoot(builder, "wandering-trader", "entities", "spawning");
|
|
+ moveFromRoot(builder, "all-chunks-are-slime-chunks", "entities", "spawning");
|
|
+ moveFromRoot(builder, "skeleton-horse-thunder-spawn-chance", "entities", "spawning");
|
|
+ moveFromRoot(builder, "iron-golems-can-spawn-in-air", "entities", "spawning");
|
|
+ moveFromRoot(builder, "alt-item-despawn-rate", "entities", "spawning");
|
|
+ moveFromRoot(builder, "count-all-mobs-for-spawning", "entities", "spawning");
|
|
+ moveFromRoot(builder, "creative-arrow-despawn-rate", "entities", "spawning");
|
|
+ moveFromRoot(builder, "non-player-arrow-despawn-rate", "entities", "spawning");
|
|
+ moveFromRoot(builder, "monster-spawn-max-light-level", "entities", "spawning");
|
|
+
|
|
+
|
|
+ moveFromRootAndRename(builder, "duplicate-uuid-saferegen-delete-range", "safe-regen-delete-range", "entities", "spawning", "duplicate-uuid");
|
|
+
|
|
+ moveFromRoot(builder, "baby-zombie-movement-modifier", "entities", "behavior");
|
|
+ moveFromRoot(builder, "disable-creeper-lingering-effect", "entities", "behavior");
|
|
+ moveFromRoot(builder, "door-breaking-difficulty", "entities", "behavior");
|
|
+ moveFromGameMechanics(builder, "disable-chest-cat-detection", "entities", "behavior");
|
|
+ moveFromGameMechanics(builder, "disable-player-crits", "entities", "behavior");
|
|
+ moveFromRoot(builder, "experience-merge-max-value", "entities", "behavior");
|
|
+ moveFromRoot(builder, "mobs-can-always-pick-up-loot", "entities", "behavior");
|
|
+ moveFromGameMechanics(builder, "nerf-pigmen-from-nether-portals", "entities", "behavior");
|
|
+ moveFromRoot(builder, "parrots-are-unaffected-by-player-movement", "entities", "behavior");
|
|
+ moveFromRoot(builder, "phantoms-do-not-spawn-on-creative-players", "entities", "behavior");
|
|
+ moveFromRoot(builder, "phantoms-only-attack-insomniacs", "entities", "behavior");
|
|
+ moveFromRoot(builder, "piglins-guard-chests", "entities", "behavior");
|
|
+ moveFromRoot(builder, "spawner-nerfed-mobs-should-jump", "entities", "behavior");
|
|
+ moveFromRoot(builder, "zombie-villager-infection-chance", "entities", "behavior");
|
|
+ moveFromRoot(builder, "zombies-target-turtle-eggs", "entities", "behavior");
|
|
+ moveFromRoot(builder, "ender-dragons-death-always-places-dragon-egg", "entities", "behavior");
|
|
+ moveFromGameMechanicsAndRename(builder, "disable-pillager-patrols", "disable", "game-mechanics", "pillager-patrols");
|
|
+ moveFromGameMechanics(builder, "pillager-patrols", "entities", "behavior");
|
|
+ moveFromRoot(builder, "should-remove-dragon", "entities", "behavior");
|
|
+
|
|
+ moveFromRootAndRename(builder, "map-item-frame-cursor-limit", "item-frame-cursor-limit", "maps");
|
|
+ moveFromRootAndRename(builder, "map-item-frame-cursor-update-interval", "item-frame-cursor-update-interval", "maps");
|
|
+
|
|
+ moveFromRootAndRename(builder, "mob-spawner-tick-rate", "mob-spawner", "tick-rates");
|
|
+ moveFromRootAndRename(builder, "container-update-tick-rate", "container-update", "tick-rates");
|
|
+ moveFromRootAndRename(builder, "grass-spread-tick-rate", "grass-spread", "tick-rates");
|
|
+
|
|
+ moveFromRoot(builder, "allow-non-player-entities-on-scoreboards", "scoreboards");
|
|
+ moveFromRoot(builder, "use-vanilla-world-scoreboard-name-coloring", "scoreboards");
|
|
+
|
|
+ moveFromRoot(builder, "disable-thunder", "environment");
|
|
+ moveFromRoot(builder, "disable-ice-and-snow", "environment");
|
|
+ moveFromRoot(builder, "optimize-explosions", "environment");
|
|
+ moveFromRoot(builder, "disable-explosion-knockback", "environment");
|
|
+ moveFromRoot(builder, "frosted-ice", "environment");
|
|
+ moveFromRoot(builder, "disable-teleportation-suffocation-check", "environment");
|
|
+ moveFromRoot(builder, "portal-create-radius", "environment");
|
|
+ moveFromRoot(builder, "portal-search-radius", "environment");
|
|
+ moveFromRoot(builder, "portal-search-vanilla-dimension-scaling", "environment");
|
|
+ moveFromRootAndRename(builder, "enable-treasure-maps", "enabled", "environment", "treasure-maps");
|
|
+ moveFromRootAndRename(builder, "treasure-maps-find-already-discovered", "find-already-discovered", "environment", "treasure-maps");
|
|
+ moveFromRoot(builder, "water-over-lava-flow-speed", "environment");
|
|
+ moveFromRoot(builder, "nether-ceiling-void-damage-height", "environment");
|
|
+
|
|
+ moveFromRoot(builder, "keep-spawn-loaded", "spawn");
|
|
+ moveFromRoot(builder, "keep-spawn-loaded-range", "spawn");
|
|
+ moveFromRoot(builder, "allow-using-signs-inside-spawn-protection", "spawn");
|
|
+
|
|
+ moveFromRoot(builder, "max-entity-collisions", "collisions");
|
|
+ moveFromRoot(builder, "allow-vehicle-collisions", "collisions");
|
|
+ moveFromRoot(builder, "fix-climbing-bypassing-cramming-rule", "collisions");
|
|
+ moveFromRoot(builder, "only-players-collide", "collisions");
|
|
+ moveFromRoot(builder, "allow-player-cramming-damage", "collisions");
|
|
+
|
|
+ moveFromRoot(builder, "falling-block-height-nerf", "fixes");
|
|
+ moveFromRoot(builder, "fix-items-merging-through-walls", "fixes");
|
|
+ moveFromRoot(builder, "prevent-tnt-from-moving-in-water", "fixes");
|
|
+ moveFromRoot(builder, "remove-corrupt-tile-entities", "fixes");
|
|
+ moveFromRoot(builder, "split-overstacked-loot", "fixes");
|
|
+ moveFromRoot(builder, "tnt-entity-height-nerf", "fixes");
|
|
+ moveFromRoot(builder, "fix-wither-targeting-bug", "fixes");
|
|
+ moveFromGameMechanics(builder, "disable-unloaded-chunk-enderpearl-exploit", "fixes");
|
|
+ moveFromGameMechanics(builder, "fix-curing-zombie-villager-discount-exploit", "fixes");
|
|
+
|
|
+ builder.addAction(path("fishing-time-range", "MaximumTicks"), TransformAction.rename("maximum"));
|
|
+ builder.addAction(path("fishing-time-range", "MinimumTicks"), TransformAction.rename("minimum"));
|
|
+
|
|
+ builder.addAction(path("generator-settings", "flat-bedrock"), (path, value) -> new Object[]{"environment", "generate-flat-bedrock"});
|
|
+ builder.addAction(path("generator-settings"), TransformAction.remove());
|
|
+
|
|
+ builder.addAction(path("game-mechanics", ConfigurationTransformation.WILDCARD_OBJECT), (path, value) -> new Object[]{"misc", path.array()[1]});
|
|
+ builder.addAction(path("game-mechanics"), TransformAction.remove());
|
|
+
|
|
+ builder.addAction(path("feature-seeds", ConfigurationTransformation.WILDCARD_OBJECT), (path, value) -> {
|
|
+ final String key = path.array()[path.size() - 1].toString();
|
|
+ if (!key.equals("generate-random-seeds-for-all")) {
|
|
+ return new Object[]{"feature-seeds", "features", key};
|
|
+ }
|
|
+ return null;
|
|
+ });
|
|
+
|
|
+ builder.addAction(path("duplicate-uuid-resolver"), (path, value) -> {
|
|
+ final WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode duplicateUUIDMode = switch (value.require(String.class)) {
|
|
+ case "regen", "regenerate", "saferegen", "saferegenerate" -> WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.SAFE_REGEN;
|
|
+ case "remove", "delete" -> WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.DELETE;
|
|
+ case "silent", "nothing" -> WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.NOTHING;
|
|
+ default -> WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.WARN;
|
|
+ };
|
|
+ value.set(duplicateUUIDMode);
|
|
+ return new Object[]{"entities", "spawning", "duplicate-uuid", "mode"};
|
|
+ });
|
|
+
|
|
+ builder.addAction(path("redstone-implementation"), (path, value) -> {
|
|
+ if (value.require(String.class).equalsIgnoreCase("alternate-current")) {
|
|
+ value.set("alternate_current");
|
|
+ }
|
|
+ return new Object[]{"misc", "redstone-implementation"};
|
|
+ });
|
|
+
|
|
+ moveToMisc(builder, "light-queue-size");
|
|
+ moveToMisc(builder, "update-pathfinding-on-block-update");
|
|
+ moveToMisc(builder, "show-sign-click-command-failure-msgs-to-player");
|
|
+ moveToMisc(builder, "max-leash-distance");
|
|
+
|
|
+ return builder.build();
|
|
+ }
|
|
+
|
|
+ private static void moveToMisc(final ConfigurationTransformation.Builder builder, String... key) {
|
|
+ moveFromRootAndRename(builder, path((Object[]) key), key[key.length - 1], "misc");
|
|
+ }
|
|
+
|
|
+ private static void moveFromGameMechanics(final ConfigurationTransformation.Builder builder, final String key, final String... parents) {
|
|
+ moveFromGameMechanicsAndRename(builder, key, key, parents);
|
|
+ }
|
|
+
|
|
+ private static void moveFromGameMechanicsAndRename(final ConfigurationTransformation.Builder builder, final String oldKey, final String newKey, final String... parents) {
|
|
+ moveFromRootAndRename(builder, path("game-mechanics", oldKey), newKey, parents);
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/type/BooleanOrDefault.java b/src/main/java/io/papermc/paper/configuration/type/BooleanOrDefault.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..3e422b74a377fa3edaf82dd960e7449c998c2912
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/configuration/type/BooleanOrDefault.java
|
|
@@ -0,0 +1,53 @@
|
|
+package io.papermc.paper.configuration.type;
|
|
+
|
|
+import org.apache.commons.lang3.BooleanUtils;
|
|
+import org.checkerframework.checker.nullness.qual.Nullable;
|
|
+import org.spongepowered.configurate.serialize.ScalarSerializer;
|
|
+import org.spongepowered.configurate.serialize.SerializationException;
|
|
+
|
|
+import java.lang.reflect.Type;
|
|
+import java.util.Locale;
|
|
+import java.util.function.Predicate;
|
|
+
|
|
+public record BooleanOrDefault(@Nullable Boolean value) {
|
|
+ private static final String DEFAULT_VALUE = "default";
|
|
+ public static final BooleanOrDefault USE_DEFAULT = new BooleanOrDefault(null);
|
|
+ public static final ScalarSerializer<BooleanOrDefault> SERIALIZER = new Serializer();
|
|
+
|
|
+ public boolean or(boolean fallback) {
|
|
+ return this.value != null && this.value;
|
|
+ }
|
|
+
|
|
+ private static final class Serializer extends ScalarSerializer<BooleanOrDefault> {
|
|
+ Serializer() {
|
|
+ super(BooleanOrDefault.class);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public BooleanOrDefault deserialize(Type type, Object obj) throws SerializationException {
|
|
+ if (obj instanceof String string) {
|
|
+ if (DEFAULT_VALUE.equalsIgnoreCase(string)) {
|
|
+ return USE_DEFAULT;
|
|
+ }
|
|
+ try {
|
|
+ return new BooleanOrDefault(BooleanUtils.toBoolean(string.toLowerCase(Locale.ENGLISH), "true", "false"));
|
|
+ } catch (IllegalArgumentException ex) {
|
|
+ throw new SerializationException(BooleanOrDefault.class, obj + "(" + type + ") is not a boolean or '" + DEFAULT_VALUE + "'", ex);
|
|
+ }
|
|
+ } else if (obj instanceof Boolean bool) {
|
|
+ return new BooleanOrDefault(bool);
|
|
+ }
|
|
+ throw new SerializationException(obj + "(" + type + ") is not a boolean or '" + DEFAULT_VALUE + "'");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected Object serialize(BooleanOrDefault item, Predicate<Class<?>> typeSupported) {
|
|
+ final @Nullable Boolean value = item.value;
|
|
+ if (value != null) {
|
|
+ return value.toString();
|
|
+ } else {
|
|
+ return DEFAULT_VALUE;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/type/DoubleOrDefault.java b/src/main/java/io/papermc/paper/configuration/type/DoubleOrDefault.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..193709f1d08e489fc51cbe11d432529768ac1449
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/configuration/type/DoubleOrDefault.java
|
|
@@ -0,0 +1,65 @@
|
|
+package io.papermc.paper.configuration.type;
|
|
+
|
|
+import org.apache.commons.lang3.math.NumberUtils;
|
|
+import org.spongepowered.configurate.serialize.ScalarSerializer;
|
|
+import org.spongepowered.configurate.serialize.SerializationException;
|
|
+
|
|
+import java.lang.reflect.Type;
|
|
+import java.util.OptionalDouble;
|
|
+import java.util.function.Predicate;
|
|
+
|
|
+@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
|
+public final class DoubleOrDefault {
|
|
+ private static final String DEFAULT_VALUE = "default";
|
|
+ public static final DoubleOrDefault USE_DEFAULT = new DoubleOrDefault(OptionalDouble.empty());
|
|
+ public static final ScalarSerializer<DoubleOrDefault> SERIALIZER = new Serializer();
|
|
+
|
|
+ private OptionalDouble value;
|
|
+
|
|
+ public DoubleOrDefault(final OptionalDouble value) {
|
|
+ this.value = value;
|
|
+ }
|
|
+
|
|
+ public OptionalDouble value() {
|
|
+ return this.value;
|
|
+ }
|
|
+
|
|
+ public void value(final OptionalDouble value) {
|
|
+ this.value = value;
|
|
+ }
|
|
+
|
|
+ public double or(final double fallback) {
|
|
+ return this.value.orElse(fallback);
|
|
+ }
|
|
+
|
|
+ private static final class Serializer extends ScalarSerializer<DoubleOrDefault> {
|
|
+ Serializer() {
|
|
+ super(DoubleOrDefault.class);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public DoubleOrDefault deserialize(final Type type, final Object obj) throws SerializationException {
|
|
+ if (obj instanceof String string) {
|
|
+ if (DEFAULT_VALUE.equalsIgnoreCase(string)) {
|
|
+ return USE_DEFAULT;
|
|
+ }
|
|
+ if (NumberUtils.isParsable(string)) {
|
|
+ return new DoubleOrDefault(OptionalDouble.of(Double.parseDouble(string)));
|
|
+ }
|
|
+ } else if (obj instanceof Number num) {
|
|
+ return new DoubleOrDefault(OptionalDouble.of(num.doubleValue()));
|
|
+ }
|
|
+ throw new SerializationException(obj + "(" + type + ") is not a double or '" + DEFAULT_VALUE + "'");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected Object serialize(final DoubleOrDefault item, final Predicate<Class<?>> typeSupported) {
|
|
+ final OptionalDouble value = item.value();
|
|
+ if (value.isPresent()) {
|
|
+ return value.getAsDouble();
|
|
+ } else {
|
|
+ return DEFAULT_VALUE;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/type/Duration.java b/src/main/java/io/papermc/paper/configuration/type/Duration.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..fdc906b106a5c6fff2675d5399650f5b793deb70
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/configuration/type/Duration.java
|
|
@@ -0,0 +1,97 @@
|
|
+package io.papermc.paper.configuration.type;
|
|
+
|
|
+import org.checkerframework.checker.nullness.qual.Nullable;
|
|
+import org.spongepowered.configurate.serialize.ScalarSerializer;
|
|
+import org.spongepowered.configurate.serialize.SerializationException;
|
|
+
|
|
+import java.lang.reflect.Type;
|
|
+import java.util.Objects;
|
|
+import java.util.function.Predicate;
|
|
+import java.util.regex.Pattern;
|
|
+
|
|
+public final class Duration {
|
|
+
|
|
+ private static final Pattern SPACE = Pattern.compile(" ");
|
|
+ private static final Pattern NOT_NUMERIC = Pattern.compile("[^-\\d.]");
|
|
+ public static final Serializer SERIALIZER = new Serializer();
|
|
+
|
|
+ private final long seconds;
|
|
+ private final String value;
|
|
+
|
|
+ private Duration(String value) {
|
|
+ this.value = value;
|
|
+ this.seconds = getSeconds(value);
|
|
+ }
|
|
+
|
|
+ public long seconds() {
|
|
+ return this.seconds;
|
|
+ }
|
|
+
|
|
+ public long ticks() {
|
|
+ return this.seconds * 20;
|
|
+ }
|
|
+
|
|
+ public String value() {
|
|
+ return this.value;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean equals(@Nullable Object o) {
|
|
+ if (this == o) return true;
|
|
+ if (o == null || getClass() != o.getClass()) return false;
|
|
+ Duration duration = (Duration) o;
|
|
+ return seconds == duration.seconds && this.value.equals(duration.value);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public int hashCode() {
|
|
+ return Objects.hash(this.seconds, this.value);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public String toString() {
|
|
+ return "Duration{" +
|
|
+ "seconds=" + this.seconds +
|
|
+ ", value='" + this.value + '\'' +
|
|
+ '}';
|
|
+ }
|
|
+
|
|
+ public static Duration of(String time) {
|
|
+ return new Duration(time);
|
|
+ }
|
|
+
|
|
+ private static int getSeconds(String str) {
|
|
+ str = SPACE.matcher(str).replaceAll("");
|
|
+ final char unit = str.charAt(str.length() - 1);
|
|
+ str = NOT_NUMERIC.matcher(str).replaceAll("");
|
|
+ double num;
|
|
+ try {
|
|
+ num = Double.parseDouble(str);
|
|
+ } catch (Exception e) {
|
|
+ num = 0D;
|
|
+ }
|
|
+ switch (unit) {
|
|
+ case 'd': num *= (double) 60*60*24; break;
|
|
+ case 'h': num *= (double) 60*60; break;
|
|
+ case 'm': num *= (double) 60; break;
|
|
+ default: case 's': break;
|
|
+ }
|
|
+ return (int) num;
|
|
+ }
|
|
+
|
|
+ private static final class Serializer extends ScalarSerializer<Duration> {
|
|
+ private Serializer() {
|
|
+ super(Duration.class);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public Duration deserialize(Type type, Object obj) throws SerializationException {
|
|
+ return new Duration(obj.toString());
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected Object serialize(Duration item, Predicate<Class<?>> typeSupported) {
|
|
+ return item.value();
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/type/EngineMode.java b/src/main/java/io/papermc/paper/configuration/type/EngineMode.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..99e90636051fa0c770ee2eafb7f0d29c8195f9ae
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/configuration/type/EngineMode.java
|
|
@@ -0,0 +1,37 @@
|
|
+package io.papermc.paper.configuration.type;
|
|
+
|
|
+import io.papermc.paper.configuration.serializer.EngineModeSerializer;
|
|
+import org.spongepowered.configurate.serialize.ScalarSerializer;
|
|
+
|
|
+public enum EngineMode {
|
|
+
|
|
+ HIDE(1, "hide ores"), OBFUSCATE(2, "obfuscate");
|
|
+
|
|
+ public static final ScalarSerializer<EngineMode> SERIALIZER = new EngineModeSerializer();
|
|
+
|
|
+ private final int id;
|
|
+ private final String description;
|
|
+
|
|
+ EngineMode(int id, String description) {
|
|
+ this.id = id;
|
|
+ this.description = description;
|
|
+ }
|
|
+
|
|
+ public static EngineMode valueOf(int id) {
|
|
+ for (EngineMode engineMode : values()) {
|
|
+ if (engineMode.getId() == id) {
|
|
+ return engineMode;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ throw new IllegalArgumentException("No enum constant with id " + id);
|
|
+ }
|
|
+
|
|
+ public int getId() {
|
|
+ return id;
|
|
+ }
|
|
+
|
|
+ public String getDescription() {
|
|
+ return description;
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/type/IntOrDefault.java b/src/main/java/io/papermc/paper/configuration/type/IntOrDefault.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..3278045dbf081cc4099e2eac3a6c4fac3012be4b
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/configuration/type/IntOrDefault.java
|
|
@@ -0,0 +1,56 @@
|
|
+package io.papermc.paper.configuration.type;
|
|
+
|
|
+import com.mojang.logging.LogUtils;
|
|
+import org.apache.commons.lang3.math.NumberUtils;
|
|
+import org.slf4j.Logger;
|
|
+import org.spongepowered.configurate.serialize.ScalarSerializer;
|
|
+import org.spongepowered.configurate.serialize.SerializationException;
|
|
+
|
|
+import java.lang.reflect.Type;
|
|
+import java.util.OptionalInt;
|
|
+import java.util.function.Predicate;
|
|
+
|
|
+public record IntOrDefault(OptionalInt value) {
|
|
+ private static final String DEFAULT_VALUE = "default";
|
|
+ private static final Logger LOGGER = LogUtils.getLogger();
|
|
+ public static final IntOrDefault USE_DEFAULT = new IntOrDefault(OptionalInt.empty());
|
|
+ public static final ScalarSerializer<IntOrDefault> SERIALIZER = new Serializer();
|
|
+
|
|
+ public int or(final int fallback) {
|
|
+ return this.value.orElse(fallback);
|
|
+ }
|
|
+
|
|
+ private static final class Serializer extends ScalarSerializer<IntOrDefault> {
|
|
+ Serializer() {
|
|
+ super(IntOrDefault.class);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public IntOrDefault deserialize(final Type type, final Object obj) throws SerializationException {
|
|
+ if (obj instanceof String string) {
|
|
+ if (DEFAULT_VALUE.equalsIgnoreCase(string)) {
|
|
+ return USE_DEFAULT;
|
|
+ }
|
|
+ if (NumberUtils.isParsable(string)) {
|
|
+ return new IntOrDefault(OptionalInt.of(Integer.parseInt(string)));
|
|
+ }
|
|
+ } else if (obj instanceof Number num) {
|
|
+ if (num.intValue() != num.doubleValue() || num.intValue() != num.longValue()) {
|
|
+ LOGGER.error("{} cannot be converted to an integer without losing information", num);
|
|
+ }
|
|
+ return new IntOrDefault(OptionalInt.of(num.intValue()));
|
|
+ }
|
|
+ throw new SerializationException(obj + "(" + type + ") is not a integer or '" + DEFAULT_VALUE + "'");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected Object serialize(final IntOrDefault item, final Predicate<Class<?>> typeSupported) {
|
|
+ final OptionalInt value = item.value();
|
|
+ if (value.isPresent()) {
|
|
+ return value.getAsInt();
|
|
+ } else {
|
|
+ return DEFAULT_VALUE;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/type/fallback/ArrowDespawnRate.java b/src/main/java/io/papermc/paper/configuration/type/fallback/ArrowDespawnRate.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..24763d3d270c29c95e0b3e85111145234f660a62
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/configuration/type/fallback/ArrowDespawnRate.java
|
|
@@ -0,0 +1,38 @@
|
|
+package io.papermc.paper.configuration.type.fallback;
|
|
+
|
|
+import org.spigotmc.SpigotWorldConfig;
|
|
+import org.spongepowered.configurate.serialize.SerializationException;
|
|
+
|
|
+import java.util.Map;
|
|
+import java.util.OptionalInt;
|
|
+import java.util.Set;
|
|
+
|
|
+public class ArrowDespawnRate extends FallbackValue.Int {
|
|
+
|
|
+ ArrowDespawnRate(Map<ContextKey<?>, Object> context, Object value) throws SerializationException {
|
|
+ super(context, fromObject(value));
|
|
+ }
|
|
+
|
|
+ private ArrowDespawnRate(Map<ContextKey<?>, Object> context) {
|
|
+ super(context, OptionalInt.empty());
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected OptionalInt process(int value) {
|
|
+ return Util.negToDef(value);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public Set<ContextKey<?>> required() {
|
|
+ return Set.of(FallbackValue.SPIGOT_WORLD_CONFIG);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected int fallback() {
|
|
+ return this.get(FallbackValue.SPIGOT_WORLD_CONFIG).arrowDespawnRate;
|
|
+ }
|
|
+
|
|
+ public static ArrowDespawnRate def(SpigotWorldConfig spigotConfig) {
|
|
+ return new ArrowDespawnRate(FallbackValue.SPIGOT_WORLD_CONFIG.singleton(spigotConfig));
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/type/fallback/AutosavePeriod.java b/src/main/java/io/papermc/paper/configuration/type/fallback/AutosavePeriod.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..0f2765b2edc63c11ba3c57ff55c536054826a995
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/configuration/type/fallback/AutosavePeriod.java
|
|
@@ -0,0 +1,39 @@
|
|
+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));
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/type/fallback/FallbackValue.java b/src/main/java/io/papermc/paper/configuration/type/fallback/FallbackValue.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..a3a1d398d783c37914fb6d646e11361afee687b8
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/configuration/type/fallback/FallbackValue.java
|
|
@@ -0,0 +1,102 @@
|
|
+package io.papermc.paper.configuration.type.fallback;
|
|
+
|
|
+import com.google.common.base.Preconditions;
|
|
+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;
|
|
+import java.util.Objects;
|
|
+import java.util.OptionalInt;
|
|
+import java.util.Set;
|
|
+import java.util.function.Supplier;
|
|
+
|
|
+@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
|
+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<Supplier<MinecraftServer>> MINECRAFT_SERVER = new ContextKey<>("MinecraftServer");
|
|
+
|
|
+ private final Map<ContextKey<?>, Object> contextMap;
|
|
+
|
|
+ protected FallbackValue(Map<ContextKey<?>, Object> contextMap) {
|
|
+ for (ContextKey<?> contextKey : this.required()) {
|
|
+ Preconditions.checkArgument(contextMap.containsKey(contextKey), contextMap + " is missing " + contextKey);
|
|
+ }
|
|
+ this.contextMap = contextMap;
|
|
+ }
|
|
+
|
|
+ protected abstract String serialize();
|
|
+
|
|
+ protected abstract Set<ContextKey<?>> required();
|
|
+
|
|
+ @SuppressWarnings("unchecked")
|
|
+ protected <T> T get(ContextKey<T> contextKey) {
|
|
+ return (T) Objects.requireNonNull(this.contextMap.get(contextKey), "Missing " + contextKey);
|
|
+ }
|
|
+
|
|
+ public non-sealed abstract static class Int extends FallbackValue {
|
|
+
|
|
+ private final OptionalInt value;
|
|
+
|
|
+ Int(Map<ContextKey<?>, Object> contextMap, OptionalInt value) {
|
|
+ super(contextMap);
|
|
+ if (value.isEmpty()) {
|
|
+ this.value = value;
|
|
+ } else {
|
|
+ this.value = this.process(value.getAsInt());
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public int value() {
|
|
+ return value.orElseGet(this::fallback);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected final String serialize() {
|
|
+ return value.isPresent() ? String.valueOf(this.value.getAsInt()) : DEFAULT_VALUE;
|
|
+ }
|
|
+
|
|
+ protected OptionalInt process(int value) {
|
|
+ return OptionalInt.of(value);
|
|
+ }
|
|
+
|
|
+ protected abstract int fallback();
|
|
+
|
|
+ protected static OptionalInt fromObject(Object obj) throws SerializationException {
|
|
+ if (obj instanceof OptionalInt optionalInt) {
|
|
+ return optionalInt;
|
|
+ } else if (obj instanceof String string) {
|
|
+ if (DEFAULT_VALUE.equalsIgnoreCase(string)) {
|
|
+ return OptionalInt.empty();
|
|
+ }
|
|
+ if (NumberUtils.isParsable(string)) {
|
|
+ return OptionalInt.of(Integer.parseInt(string));
|
|
+ }
|
|
+ } else if (obj instanceof Integer num) {
|
|
+ return OptionalInt.of(num);
|
|
+ }
|
|
+ throw new SerializationException(obj + " is not a integer or '" + DEFAULT_VALUE + "'");
|
|
+ }
|
|
+ }
|
|
+
|
|
+ static class ContextKey<T> {
|
|
+
|
|
+ private final String name;
|
|
+
|
|
+ ContextKey(String name) {
|
|
+ this.name = name;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public String toString() {
|
|
+ return this.name;
|
|
+ }
|
|
+
|
|
+ Map<ContextKey<?>, Object> singleton(T value) {
|
|
+ return Map.of(this, value);
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/type/fallback/FallbackValueSerializer.java b/src/main/java/io/papermc/paper/configuration/type/fallback/FallbackValueSerializer.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..8d0fcd038e12c70a3a5aaf2669452589d9055255
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/configuration/type/fallback/FallbackValueSerializer.java
|
|
@@ -0,0 +1,55 @@
|
|
+package io.papermc.paper.configuration.type.fallback;
|
|
+
|
|
+import net.minecraft.server.MinecraftServer;
|
|
+import org.checkerframework.checker.nullness.qual.Nullable;
|
|
+import org.spigotmc.SpigotWorldConfig;
|
|
+import org.spongepowered.configurate.serialize.ScalarSerializer;
|
|
+import org.spongepowered.configurate.serialize.SerializationException;
|
|
+
|
|
+import java.lang.reflect.Type;
|
|
+import java.util.HashMap;
|
|
+import java.util.Map;
|
|
+import java.util.function.Predicate;
|
|
+import java.util.function.Supplier;
|
|
+
|
|
+import static io.leangen.geantyref.GenericTypeReflector.erase;
|
|
+
|
|
+public class FallbackValueSerializer extends ScalarSerializer<FallbackValue> {
|
|
+
|
|
+ private static final Map<Class<?>, FallbackCreator<?>> REGISTRY = new HashMap<>();
|
|
+
|
|
+ static {
|
|
+ REGISTRY.put(ArrowDespawnRate.class, ArrowDespawnRate::new);
|
|
+ REGISTRY.put(AutosavePeriod.class, AutosavePeriod::new);
|
|
+ }
|
|
+
|
|
+ FallbackValueSerializer(Map<FallbackValue.ContextKey<?>, Object> contextMap) {
|
|
+ super(FallbackValue.class);
|
|
+ this.contextMap = contextMap;
|
|
+ }
|
|
+
|
|
+ @FunctionalInterface
|
|
+ private interface FallbackCreator<T extends FallbackValue> {
|
|
+ T create(Map<FallbackValue.ContextKey<?>, Object> context, Object value) throws SerializationException;
|
|
+ }
|
|
+
|
|
+ private final Map<FallbackValue.ContextKey<?>, Object> contextMap;
|
|
+
|
|
+ @Override
|
|
+ public FallbackValue deserialize(Type type, Object obj) throws SerializationException {
|
|
+ final @Nullable FallbackCreator<?> creator = REGISTRY.get(erase(type));
|
|
+ if (creator == null) {
|
|
+ throw new SerializationException(type + " does not have a FallbackCreator registered");
|
|
+ }
|
|
+ return creator.create(this.contextMap, obj);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected Object serialize(FallbackValue item, Predicate<Class<?>> typeSupported) {
|
|
+ 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));
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/type/fallback/Util.java b/src/main/java/io/papermc/paper/configuration/type/fallback/Util.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..70cc7b45e7355f6c8476a74a070f1266e4cca189
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/configuration/type/fallback/Util.java
|
|
@@ -0,0 +1,10 @@
|
|
+package io.papermc.paper.configuration.type.fallback;
|
|
+
|
|
+import java.util.OptionalInt;
|
|
+
|
|
+final class Util {
|
|
+
|
|
+ static OptionalInt negToDef(int value) {
|
|
+ return value < 0 ? OptionalInt.empty() : OptionalInt.of(value);
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java
|
|
index 853e7c2019f5147e9681e95a82eaef0825b6341e..a48a12a31a3d09a9373b688dcc093035f8f8a300 100644
|
|
--- a/src/main/java/net/minecraft/server/Main.java
|
|
+++ b/src/main/java/net/minecraft/server/Main.java
|
|
@@ -110,6 +110,11 @@ public class Main {
|
|
DedicatedServerSettings dedicatedserversettings = new DedicatedServerSettings(optionset); // CraftBukkit - CLI argument support
|
|
|
|
dedicatedserversettings.forceSave();
|
|
+ // Paper start - load config files for access below if needed
|
|
+ org.bukkit.configuration.file.YamlConfiguration bukkitConfiguration = io.papermc.paper.configuration.PaperConfigurations.loadLegacyConfigFile((File) optionset.valueOf("bukkit-settings"));
|
|
+ org.bukkit.configuration.file.YamlConfiguration spigotConfiguration = io.papermc.paper.configuration.PaperConfigurations.loadLegacyConfigFile((File) optionset.valueOf("spigot-settings"));
|
|
+ // Paper end
|
|
+
|
|
Path path1 = Paths.get("eula.txt");
|
|
Eula eula = new Eula(path1);
|
|
|
|
@@ -133,7 +138,7 @@ public class Main {
|
|
}
|
|
|
|
File file = (File) optionset.valueOf("universe"); // CraftBukkit
|
|
- Services services = Services.create(new YggdrasilAuthenticationService(Proxy.NO_PROXY), file);
|
|
+ Services services = Services.create(new YggdrasilAuthenticationService(Proxy.NO_PROXY), file, optionset); // Paper
|
|
// CraftBukkit start
|
|
String s = (String) Optional.ofNullable((String) optionset.valueOf("world")).orElse(dedicatedserversettings.getProperties().levelName);
|
|
LevelStorageSource convertable = LevelStorageSource.createDefault(file.toPath());
|
|
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
index e7b1456a123208241d0b1c5956a137d6a5cfbfcd..7740e69617c3d543a67ed0942ba8ec550ad4386d 100644
|
|
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
@@ -281,6 +281,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
private static final int SAMPLE_INTERVAL = 100;
|
|
public final double[] recentTps = new double[ 3 ];
|
|
// Spigot end
|
|
+ public final io.papermc.paper.configuration.PaperConfigurations paperConfigurations;
|
|
|
|
public static <S extends MinecraftServer> S spin(Function<Thread, S> serverFactory) {
|
|
AtomicReference<S> atomicreference = new AtomicReference();
|
|
@@ -371,6 +372,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
}
|
|
}
|
|
Runtime.getRuntime().addShutdownHook(new org.bukkit.craftbukkit.util.ServerShutdownThread(this));
|
|
+ this.paperConfigurations = services.paperConfigurations(); // Paper
|
|
}
|
|
// CraftBukkit end
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/Services.java b/src/main/java/net/minecraft/server/Services.java
|
|
index 697ca7457115423a8c4d8a7d1f7a353237b56509..d7d65d0faefa5551480a4090de3a881828238ffd 100644
|
|
--- a/src/main/java/net/minecraft/server/Services.java
|
|
+++ b/src/main/java/net/minecraft/server/Services.java
|
|
@@ -7,14 +7,30 @@ import java.io.File;
|
|
import net.minecraft.server.players.GameProfileCache;
|
|
import net.minecraft.util.SignatureValidator;
|
|
|
|
-public record Services(MinecraftSessionService sessionService, SignatureValidator serviceSignatureValidator, GameProfileRepository profileRepository, GameProfileCache profileCache) {
|
|
+// Paper start
|
|
+public record Services(MinecraftSessionService sessionService, SignatureValidator serviceSignatureValidator, GameProfileRepository profileRepository, GameProfileCache profileCache, @javax.annotation.Nullable io.papermc.paper.configuration.PaperConfigurations paperConfigurations) {
|
|
+
|
|
+ public Services(MinecraftSessionService sessionService, SignatureValidator signatureValidator, GameProfileRepository profileRepository, GameProfileCache profileCache) {
|
|
+ this(sessionService, signatureValidator, profileRepository, profileCache, null);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public io.papermc.paper.configuration.PaperConfigurations paperConfigurations() {
|
|
+ return java.util.Objects.requireNonNull(this.paperConfigurations);
|
|
+ }
|
|
+ // Paper end
|
|
private static final String USERID_CACHE_FILE = "usercache.json";
|
|
|
|
- public static Services create(YggdrasilAuthenticationService authenticationService, File rootDirectory) {
|
|
+ public static Services create(YggdrasilAuthenticationService authenticationService, File rootDirectory, joptsimple.OptionSet optionSet) throws Exception { // Paper
|
|
MinecraftSessionService minecraftSessionService = authenticationService.createMinecraftSessionService();
|
|
GameProfileRepository gameProfileRepository = authenticationService.createProfileRepository();
|
|
GameProfileCache gameProfileCache = new GameProfileCache(gameProfileRepository, new File(rootDirectory, "usercache.json"));
|
|
SignatureValidator signatureValidator = SignatureValidator.from(authenticationService.getServicesKey());
|
|
- return new Services(minecraftSessionService, signatureValidator, gameProfileRepository, gameProfileCache);
|
|
+ // Paper start
|
|
+ final java.nio.file.Path legacyConfigPath = ((File) optionSet.valueOf("paper-settings")).toPath();
|
|
+ final java.nio.file.Path configDirPath = ((File) optionSet.valueOf("paper-settings-directory")).toPath();
|
|
+ io.papermc.paper.configuration.PaperConfigurations paperConfigurations = io.papermc.paper.configuration.PaperConfigurations.setup(legacyConfigPath, configDirPath, rootDirectory.toPath(), (File) optionSet.valueOf("spigot-settings"));
|
|
+ return new Services(minecraftSessionService, signatureValidator, gameProfileRepository, gameProfileCache, paperConfigurations);
|
|
+ // Paper end
|
|
}
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
|
|
index 7d965247833c91dc824e6cc56e8b0fe5f3413d1d..08ae7a96e93c0d8547f560b3f753804525621c6b 100644
|
|
--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
|
|
+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
|
|
@@ -188,6 +188,10 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
|
|
org.spigotmc.SpigotConfig.init((java.io.File) options.valueOf("spigot-settings"));
|
|
org.spigotmc.SpigotConfig.registerCommands();
|
|
// Spigot end
|
|
+ // Paper start
|
|
+ paperConfigurations.initializeGlobalConfiguration();
|
|
+ paperConfigurations.initializeWorldDefaultsConfiguration();
|
|
+ // Paper end
|
|
|
|
this.setPvpAllowed(dedicatedserverproperties.pvp);
|
|
this.setFlightAllowed(dedicatedserverproperties.allowFlight);
|
|
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
index 3f4dbd3503bc3224a2f483563af25a76cc0b68f9..1a23437b9fa17846fd28163ae930d21a6bb00138 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
@@ -227,7 +227,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
|
|
public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey<Level> resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List<CustomSpawner> list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) {
|
|
// Holder holder = worlddimension.typeHolder(); // CraftBukkit - decompile error
|
|
// Objects.requireNonNull(minecraftserver); // CraftBukkit - decompile error
|
|
- super(iworlddataserver, resourcekey, worlddimension.typeHolder(), minecraftserver::getProfiler, false, flag, i, minecraftserver.getMaxChainedNeighborUpdates(), gen, biomeProvider, env);
|
|
+ super(iworlddataserver, resourcekey, worlddimension.typeHolder(), minecraftserver::getProfiler, false, flag, i, minecraftserver.getMaxChainedNeighborUpdates(), gen, biomeProvider, env, spigotConfig -> minecraftserver.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(convertable_conversionsession.levelDirectory.path(), iworlddataserver.getLevelName(), resourcekey.location(), spigotConfig))); // Paper
|
|
this.pvpMode = minecraftserver.isPvpAllowed();
|
|
this.convertable = convertable_conversionsession;
|
|
this.uuid = WorldUUID.getUUID(convertable_conversionsession.levelDirectory.path().toFile());
|
|
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
|
|
index c1194f459414dc6ca9626ab8cec48cb48cdd926b..649df119b24dc8c390f45e9f813cf8c37994e0cf 100644
|
|
--- a/src/main/java/net/minecraft/world/level/Level.java
|
|
+++ b/src/main/java/net/minecraft/world/level/Level.java
|
|
@@ -149,6 +149,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
|
|
public final it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap<SpawnCategory> ticksPerSpawnCategory = new it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap<>();
|
|
public boolean populating;
|
|
public final org.spigotmc.SpigotWorldConfig spigotConfig; // Spigot
|
|
+ // Paper start
|
|
+ private final io.papermc.paper.configuration.WorldConfiguration paperConfig;
|
|
+ public io.papermc.paper.configuration.WorldConfiguration paperConfig() {
|
|
+ return this.paperConfig;
|
|
+ }
|
|
+ // Paper end
|
|
|
|
public final SpigotTimings.WorldTimingsHandler timings; // Spigot
|
|
public static BlockPos lastPhysicsProblem; // Spigot
|
|
@@ -166,8 +172,9 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
|
|
|
|
public abstract ResourceKey<LevelStem> getTypeKey();
|
|
|
|
- protected Level(WritableLevelData worlddatamutable, ResourceKey<Level> resourcekey, Holder<DimensionType> holder, Supplier<ProfilerFiller> supplier, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env) {
|
|
+ protected Level(WritableLevelData worlddatamutable, ResourceKey<Level> resourcekey, Holder<DimensionType> holder, Supplier<ProfilerFiller> supplier, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function<org.spigotmc.SpigotWorldConfig, io.papermc.paper.configuration.WorldConfiguration> paperWorldConfigCreator) { // Paper
|
|
this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot
|
|
+ this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper
|
|
this.generator = gen;
|
|
this.world = new CraftWorld((ServerLevel) this, gen, biomeProvider, env);
|
|
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
|
index dbaec6fc4967d8140fd5af68456894bad1a0205a..4f67f7541ff257f35701610760fb8e04e8379fe3 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
|
@@ -870,6 +870,7 @@ public final class CraftServer implements Server {
|
|
}
|
|
|
|
org.spigotmc.SpigotConfig.init((File) console.options.valueOf("spigot-settings")); // Spigot
|
|
+ this.console.paperConfigurations.reloadConfigs(this.console);
|
|
for (ServerLevel world : this.console.getAllLevels()) {
|
|
world.serverLevelData.setDifficulty(config.difficulty);
|
|
world.setSpawnSettings(config.spawnMonsters, config.spawnAnimals);
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java
|
|
index f264607eb3df1f247c7c36d328405900b52df38e..b05cea49219c6582bacc705f41e72ca7c7eb6a8c 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/Main.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/Main.java
|
|
@@ -129,6 +129,19 @@ public class Main {
|
|
.defaultsTo(new File("spigot.yml"))
|
|
.describedAs("Yml file");
|
|
// Spigot End
|
|
+
|
|
+ // Paper Start
|
|
+ acceptsAll(asList("paper-dir", "paper-settings-directory"), "Directory for Paper settings")
|
|
+ .withRequiredArg()
|
|
+ .ofType(File.class)
|
|
+ .defaultsTo(new File(io.papermc.paper.configuration.PaperConfigurations.CONFIG_DIR))
|
|
+ .describedAs("Config directory");
|
|
+ acceptsAll(asList("paper", "paper-settings"), "File for Paper settings")
|
|
+ .withRequiredArg()
|
|
+ .ofType(File.class)
|
|
+ .defaultsTo(new File("paper.yml"))
|
|
+ .describedAs("Yml file");
|
|
+ // Paper end
|
|
}
|
|
};
|
|
|
|
diff --git a/src/main/java/org/spigotmc/SpigotConfig.java b/src/main/java/org/spigotmc/SpigotConfig.java
|
|
index a96cb7a5f7c94cd9a46b31cf8ec90b544221557b..7c35d86eac0d69ba4be48faf364fd6dc84fa7e87 100644
|
|
--- a/src/main/java/org/spigotmc/SpigotConfig.java
|
|
+++ b/src/main/java/org/spigotmc/SpigotConfig.java
|
|
@@ -96,7 +96,7 @@ public class SpigotConfig
|
|
}
|
|
}
|
|
|
|
- static void readConfig(Class<?> clazz, Object instance)
|
|
+ public static void readConfig(Class<?> clazz, Object instance) // Paper - package-private -> public
|
|
{
|
|
for ( Method method : clazz.getDeclaredMethods() )
|
|
{
|
|
diff --git a/src/main/java/org/spigotmc/SpigotWorldConfig.java b/src/main/java/org/spigotmc/SpigotWorldConfig.java
|
|
index bd0bf398f900302187f3436119c754592d575416..d139cbcf0b159372f229bef6ae49b45a74c163ad 100644
|
|
--- a/src/main/java/org/spigotmc/SpigotWorldConfig.java
|
|
+++ b/src/main/java/org/spigotmc/SpigotWorldConfig.java
|
|
@@ -58,8 +58,14 @@ public class SpigotWorldConfig
|
|
|
|
public int getInt(String path, int def)
|
|
{
|
|
- this.config.addDefault( "world-settings.default." + path, def );
|
|
- return this.config.getInt( "world-settings." + this.worldName + "." + path, this.config.getInt( "world-settings.default." + path ) );
|
|
+ // 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)
|
|
diff --git a/src/test/java/io/papermc/paper/configuration/GlobalConfigTestingBase.java b/src/test/java/io/papermc/paper/configuration/GlobalConfigTestingBase.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..0396589795da1f83ddf62426236dde9a3afa1376
|
|
--- /dev/null
|
|
+++ b/src/test/java/io/papermc/paper/configuration/GlobalConfigTestingBase.java
|
|
@@ -0,0 +1,20 @@
|
|
+package io.papermc.paper.configuration;
|
|
+
|
|
+import org.spongepowered.configurate.ConfigurationNode;
|
|
+import org.spongepowered.configurate.serialize.SerializationException;
|
|
+
|
|
+public final class GlobalConfigTestingBase {
|
|
+
|
|
+ public static void setupGlobalConfigForTest() {
|
|
+ //noinspection ConstantConditions
|
|
+ if (GlobalConfiguration.get() == null) {
|
|
+ ConfigurationNode node = PaperConfigurations.createForTesting();
|
|
+ try {
|
|
+ GlobalConfiguration globalConfiguration = node.require(GlobalConfiguration.class);
|
|
+ GlobalConfiguration.set(globalConfiguration);
|
|
+ } catch (SerializationException e) {
|
|
+ throw new RuntimeException(e);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/test/java/org/bukkit/support/AbstractTestingBase.java b/src/test/java/org/bukkit/support/AbstractTestingBase.java
|
|
index 6816d8a9fa504ca5a25fa62c0f0974e3e744ead6..e73a9a957cd55bf838e301ed531295162f2cfb89 100644
|
|
--- a/src/test/java/org/bukkit/support/AbstractTestingBase.java
|
|
+++ b/src/test/java/org/bukkit/support/AbstractTestingBase.java
|
|
@@ -46,6 +46,7 @@ public abstract class AbstractTestingBase {
|
|
|
|
DummyServer.setup();
|
|
DummyEnchantments.setup();
|
|
+ io.papermc.paper.configuration.GlobalConfigTestingBase.setupGlobalConfigForTest(); // Paper
|
|
|
|
ImmutableList.Builder<Material> builder = ImmutableList.builder();
|
|
for (Material m : Material.values()) {
|