Paper/patches/server/0005-Paper-config-files.patch

5377 lines
255 KiB
Diff
Raw Normal View History

2021-06-11 14:02:28 +02:00
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
2021-06-11 14:02:28 +02:00
Subject: [PATCH] Paper config files
== AT ==
public org.spigotmc.SpigotWorldConfig getBoolean(Ljava/lang/String;Z)Z
public org.spigotmc.SpigotWorldConfig getDouble(Ljava/lang/String;)D
public org.spigotmc.SpigotWorldConfig getDouble(Ljava/lang/String;D)D
public org.spigotmc.SpigotWorldConfig getInt(Ljava/lang/String;)I
public org.spigotmc.SpigotWorldConfig getInt(Ljava/lang/String;I)I
public org.spigotmc.SpigotWorldConfig getList(Ljava/lang/String;Ljava/lang/Object;)Ljava/util/List;
public org.spigotmc.SpigotWorldConfig getString(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
public net.minecraft.server.dedicated.DedicatedServerProperties reload(Lnet/minecraft/core/RegistryAccess;Ljava/util/Properties;Ljoptsimple/OptionSet;)Lnet/minecraft/server/dedicated/DedicatedServerProperties;
public net.minecraft.world.level.NaturalSpawner SPAWNING_CATEGORIES
2021-06-11 14:02:28 +02:00
diff --git a/build.gradle.kts b/build.gradle.kts
2024-10-22 19:28:57 +02:00
index 41b000aaa71dca3fb392ae657be16e05bd37a178..da6b4787fa787e098e4031790e955ce616593ee9 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -10,6 +10,7 @@ dependencies {
Updated Upstream (Bukkit/CraftBukkit/Spigot) (#10164) Upstream has released updates that appear to apply and compile correctly. This update has not been tested by PaperMC and as with ANY update, please do your own testing Bukkit Changes: 63c208dd Remove no longer used import 70be76c7 PR-958: Further clarify deprecation of TAG_CONTAINER_ARRAY ae21f4ac PR-955: Add methods to place structures with block/entity transformers e3d960f2 SPIGOT-7547: Remark that Damageable#setAbsorptionAmount() is capped to a specific value b125516c Fix typo in RecipeChoice.ExactChoice docs 309497c1 Add EntityMountEvent and EntityDismount Event 2fd45ae3 Improve ItemFactory#enchantItem consistency 2b198268 PR-933: Define native persistent data types for lists CraftBukkit Changes: 771182f70 PR-1327: Add methods to place structures with block/entity transformers e41ad4c82 SPIGOT-7567: SpawnReason for SNOWMAN is reported as BUILD_IRONGOLEM 76931e8bd Add EntityMountEvent and EntityDismount Event 9b29b21c7 PR-1183: Better handle lambda expression and renaming of classes in Commodore 1462ebe85 Reformat Commodore.java 9fde4c037 PR-1324: Improve ItemFactory#enchantItem consistency 4e419c774 PR-1295: Define native persistent data types for lists dd8cca388 SPIGOT-7562: Fix Score#getScore and Score#isScoreSet 690278200 Only fetch an online UUID in online mode 1da8d9a53 Fire PreLogin events even in offline mode 2e88514ad PR-1325: Use CraftBlockType and CraftItemType instead of CraftMagicNumbers to convert between minecraft and bukkit block / item representation Spigot Changes: 864e4acc Restore accidentally removed package-info.java f91a10d5 Remove obsolete EntityMountEvent and EntityDismountEvent 828f0593 SPIGOT-7558: Deprecate silenceable lightning API as sound is now client-side and cannot be removed cdc4e035 Remove obsolete patch fetching correct mode UUIDs 49e36b8e Merge related BungeeCord patches 6e87b9ab Remove obsolete firing of PreLogin events in offline mode 5c76b183 Remove redundant patch dealing with exceptions in the crash reporter 3a2219d1 Remove redundant patch logging cause of unexpected exception
2024-01-14 10:46:04 +01:00
implementation("jline:jline:2.12.1")
2024-04-23 20:03:16 +02:00
implementation("org.apache.logging.log4j:log4j-iostreams:2.22.1") // Paper - remove exclusion
Updated Upstream (Bukkit/CraftBukkit) (#11501) Upstream has released updates that appear to apply and compile correctly. This update has not been tested by PaperMC and as with ANY update, please do your own testing Bukkit Changes: bb4e97c6 Add support for Java 23 bc6874dd Bump asm to 9.7.1 50e8a00b PR-1064: Add specific getTopInventory methods for InventoryView derivatives 758b0a0f SPIGOT-7911: Fix Location#isWorldLoaded() for re-loaded worlds 133a64a7 Improve Registry#getOrThrow messages be0f5957 PR-1058: Add tests for Minecraft registry <-> Bukkit fields d1b31df2 PR-1062: Clarify BeaconView documentation 3fab4384 PR-1060: Cache Material to BlockType and ItemType conversion 967a7301 SPIGOT-7906: Increase YAML nesting limit to 100 6ecf033d SPIGOT-7899: Smithing recipes don't require inputs CraftBukkit Changes: 0a7bd6c81 PR-1493: Improve reroute performance and add some tests 54941524c Add support for Java 23 f4d957fff SPIGOT-7915: Fix World#getKeepSpawnInMemory() using Spawn Radius rather than Spawn Chunk Radius ded183674 Fix HIDE_ENCHANTS flag in items without enchantments 308785a0a Bump asm to 9.7.1 and re-add ClassReader to ClassWriter 72ce823cd PR-1487: Add specific getTopInventory methods for InventoryView derivatives 11a5e840c SPIGOT-7907, PR-1484: Improve merchant recipe item matching behavior to more closely align with older versions 45b66f7e4 SPIGOT-7909: Always set HIDE_ENCHANTS flag to item if flag is set 963459791 Increase outdated build delay fc5b2d75f SPIGOT-7910: Fix launching breeze wind charge from API and improve dispenser launch API c7d6428f2 SPIGOT-7856, PR-1483: End platform not dropping items after replacing blocks 2a5572b52 SPIGOT-7780, PR-1482: Cannot edit chunks during unload event 527041ab5 SPIGOT-7902, PR-1477: Fix CraftMetaPotion#hasCustomEffects() does not check if customEffects (List) is empty 5529a1769 Implement base methods for tags 30fbdbaaf Improve Registry#getOrThrow messages 6b71a7322 PR-1475: Add tests for Minecraft registry <-> Bukkit fields 5f24c255c SPIGOT-7908: Mark junit-platform-suite-engine as test scope e4c92ef65 PR-1473: Change tests to use suites, to run tests in different environments and feature flags d25e1e722 PR-1481: Fix BeaconView#set[X]Effect(null) d69a05362 PR-1480: Fix PerMaterialTest#isEdible test running for legacy materials bb3284a89 PR-1479: Use custom #isBlock method in legacy init instead of the one in Material, since it relies on legacy being init 98c57cbbe SPIGOT-7904: Fix NPE for PlayerItemBreakEvent f35bae9ec Fix missing hasJukeboxPlayable 8a6f8b6d8 SPIGOT-7881: CTRL+Pick Block saves position data into item 7913b3be7 SPIGOT-7899: Smithing recipes don't require inputs
2024-10-21 00:06:54 +02:00
implementation("org.ow2.asm:asm-commons:9.7.1")
2023-10-04 03:06:23 +02:00
+ implementation("org.spongepowered:configurate-yaml:4.2.0-SNAPSHOT") // Paper - config files
implementation("commons-lang:commons-lang:2.6")
2024-10-22 19:28:57 +02:00
runtimeOnly("org.xerial:sqlite-jdbc:3.46.1.3")
runtimeOnly("com.mysql:mysql-connector-j:9.1.0")
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
2021-06-11 14:02:28 +02:00
new file mode 100644
index 0000000000000000000000000000000000000000..817fd26cc3591f9cae0f61f4036dde43c4ed60e8
2021-06-11 14:02:28 +02:00
--- /dev/null
+++ b/src/main/java/io/papermc/paper/configuration/Configuration.java
@@ -0,0 +1,13 @@
+package io.papermc.paper.configuration;
2021-06-11 14:02:28 +02:00
+
+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
2022-06-11 22:43:59 +02:00
index 0000000000000000000000000000000000000000..227039a6c69c4c99bbd9c674b3aab0ef5e2c1374
--- /dev/null
+++ b/src/main/java/io/papermc/paper/configuration/ConfigurationLoaders.java
2022-06-11 22:43:59 +02:00
@@ -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)
2022-06-11 22:43:59 +02:00
+ .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
2023-10-04 03:06:23 +02:00
index 0000000000000000000000000000000000000000..042478cf7ce150f1f1bc5cddd7fa40f86ec773dd
--- /dev/null
+++ b/src/main/java/io/papermc/paper/configuration/ConfigurationPart.java
2023-10-04 03:06:23 +02:00
@@ -0,0 +1,7 @@
+package io.papermc.paper.configuration;
+
2023-10-04 03:06:23 +02:00
+/**
+ * Marker interface for unique sections of a configuration.
+ */
+public abstract class ConfigurationPart {
+}
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
2024-06-14 14:11:52 +02:00
index 0000000000000000000000000000000000000000..d9502ba028a96f9cc846f9ed428bd8066b857ca3
--- /dev/null
+++ b/src/main/java/io/papermc/paper/configuration/Configurations.java
@@ -0,0 +1,360 @@
+package io.papermc.paper.configuration;
+
+import com.google.common.base.Preconditions;
+import com.mojang.logging.LogUtils;
2022-06-09 21:51:26 +02:00
+import io.leangen.geantyref.TypeToken;
+import io.papermc.paper.configuration.constraint.Constraint;
+import io.papermc.paper.configuration.constraint.Constraints;
+import net.minecraft.core.RegistryAccess;
2022-06-09 21:51:26 +02:00
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.world.level.GameRules;
2022-06-09 21:51:26 +02:00
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jetbrains.annotations.MustBeInvokedByOverriders;
+import org.slf4j.Logger;
+import org.spongepowered.configurate.CommentedConfigurationNode;
+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.serialize.SerializationException;
+import org.spongepowered.configurate.util.CheckedFunction;
+import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
2021-06-11 14:02:28 +02:00
+
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.nio.file.AccessDeniedException;
+import java.nio.file.Files;
+import java.nio.file.Path;
2022-06-09 21:51:26 +02:00
+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> {
+
+ private static final Logger LOGGER = LogUtils.getClassLogger();
+ public static final String WORLD_DEFAULTS = "__world_defaults__";
2024-06-14 14:11:52 +02:00
+ public static final ResourceLocation WORLD_DEFAULTS_KEY = ResourceLocation.fromNamespaceAndPath("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 abstract int globalConfigVersion();
+
+ protected abstract int worldConfigVersion();
+
+ protected ObjectMapper.Factory.Builder createGlobalObjectMapperFactoryBuilder() {
+ return this.createObjectMapper();
+ }
+
+ @MustBeInvokedByOverriders
+ protected YamlConfigurationLoader.Builder createGlobalLoaderBuilder() {
+ return this.createLoaderBuilder();
+ }
+
+ static <T> CheckedFunction<ConfigurationNode, T, SerializationException> creator(final Class<? extends T> type, final boolean refreshNode) {
+ return node -> {
+ final 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(final RegistryAccess registryAccess) throws ConfigurateException {
+ return this.initializeGlobalConfiguration(creator(this.globalConfigClass, true));
+ }
+
+ private void trySaveFileNode(YamlConfigurationLoader loader, ConfigurationNode node, String filename) throws ConfigurateException {
+ try {
+ loader.save(node);
+ } catch (ConfigurateException ex) {
+ if (ex.getCause() instanceof AccessDeniedException) {
+ LOGGER.warn("Could not save {}: Paper could not persist the full set of configuration settings in the configuration file. Any setting missing from the configuration file will be set with its default value in memory. Admins should make sure to review the configuration documentation at https://docs.papermc.io/paper/configuration for more details.", filename, ex);
+ } else throw ex;
+ }
+ }
+
+ 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.notExists(configFile)) {
+ node = CommentedConfigurationNode.root(loader.defaultOptions());
+ node.node(Configuration.VERSION_FIELD).raw(this.globalConfigVersion());
+ } else {
+ node = loader.load();
+ this.verifyGlobalConfigVersion(node);
+ }
+ this.applyGlobalConfigTransformations(node);
+ final G instance = creator.apply(node);
+ trySaveFileNode(loader, node, configFile.toString());
+ return instance;
+ }
+
+ protected void verifyGlobalConfigVersion(final ConfigurationNode globalNode) {
+ final ConfigurationNode version = globalNode.node(Configuration.VERSION_FIELD);
+ if (version.virtual()) {
+ LOGGER.warn("The global config file didn't have a version set, assuming latest");
+ version.raw(this.globalConfigVersion());
+ } else if (version.getInt() > this.globalConfigVersion()) {
+ LOGGER.error("Loading a newer configuration than is supported ({} > {})! You may have to backup & delete your global config file to start the server.", version.getInt(), this.globalConfigVersion());
+ }
+ }
+
+ protected void applyGlobalConfigTransformations(final ConfigurationNode node) throws ConfigurateException {
+ }
+
2022-06-09 21:51:26 +02:00
+ @MustBeInvokedByOverriders
+ protected ContextMap.Builder createDefaultContextMap(final RegistryAccess registryAccess) {
2022-06-09 21:51:26 +02:00
+ return ContextMap.builder()
+ .put(WORLD_NAME, WORLD_DEFAULTS)
+ .put(WORLD_KEY, WORLD_DEFAULTS_KEY)
+ .put(REGISTRY_ACCESS, registryAccess);
2022-06-09 21:51:26 +02:00
+ }
+
+ public void initializeWorldDefaultsConfiguration(final RegistryAccess registryAccess) throws ConfigurateException {
+ final ContextMap contextMap = this.createDefaultContextMap(registryAccess)
2022-06-09 21:51:26 +02:00
+ .put(FIRST_DEFAULT)
+ .build();
+ final Path configFile = this.globalFolder.resolve(this.defaultWorldConfigFileName);
+ final DefaultWorldLoader result = this.createDefaultWorldLoader(false, contextMap, configFile);
2022-06-10 07:03:37 +02:00
+ final YamlConfigurationLoader loader = result.loader();
+ final ConfigurationNode node = loader.load();
2022-06-10 07:03:37 +02:00
+ if (result.isNewFile()) { // add version to new files
+ node.node(Configuration.VERSION_FIELD).raw(this.worldConfigVersion());
+ } else {
+ this.verifyWorldConfigVersion(contextMap, node);
2022-06-10 07:03:37 +02:00
+ }
+ this.applyWorldConfigTransformations(contextMap, node, null);
+ final W instance = node.require(this.worldConfigClass);
+ node.set(this.worldConfigClass, instance);
+ this.trySaveFileNode(loader, node, configFile.toString());
+ }
+
+ private DefaultWorldLoader createDefaultWorldLoader(final boolean requireFile, final ContextMap contextMap, final Path configFile) {
2022-06-10 07:03:37 +02:00
+ boolean willCreate = Files.notExists(configFile);
+ if (requireFile && willCreate) {
+ throw new IllegalStateException("World defaults configuration file '" + configFile + "' doesn't exist");
+ }
2022-06-10 07:03:37 +02:00
+ return new DefaultWorldLoader(
+ this.createWorldConfigLoaderBuilder(contextMap)
+ .defaultOptions(this.applyObjectMapperFactory(this.createWorldObjectMapperFactoryBuilder(contextMap).build()))
+ .path(configFile)
+ .build(),
+ willCreate
+ );
+ }
+
+ private record DefaultWorldLoader(YamlConfigurationLoader loader, boolean isNewFile) {
+ }
+
2022-06-09 21:51:26 +02:00
+ protected ObjectMapper.Factory.Builder createWorldObjectMapperFactoryBuilder(final ContextMap contextMap) {
+ return this.createObjectMapper();
+ }
+
+ @MustBeInvokedByOverriders
2022-06-09 21:51:26 +02:00
+ protected YamlConfigurationLoader.Builder createWorldConfigLoaderBuilder(final ContextMap contextMap) {
+ return this.createLoaderBuilder();
+ }
+
+ // Make sure to run version transforms on the default world config first via #setupWorldDefaultsConfig
2022-06-09 21:51:26 +02:00
+ public W createWorldConfig(final ContextMap contextMap) throws IOException {
+ return this.createWorldConfig(contextMap, creator(this.worldConfigClass, false));
+ }
+
2022-06-09 21:51:26 +02:00
+ protected W createWorldConfig(final ContextMap contextMap, final CheckedFunction<ConfigurationNode, W, SerializationException> creator) throws IOException {
+ Preconditions.checkArgument(!contextMap.isDefaultWorldContext(), "cannot create world map with default world context");
+ final Path defaultsConfigFile = this.globalFolder.resolve(this.defaultWorldConfigFileName);
+ final YamlConfigurationLoader defaultsLoader = this.createDefaultWorldLoader(true, this.createDefaultContextMap(contextMap.require(REGISTRY_ACCESS)).build(), defaultsConfigFile).loader();
+ final ConfigurationNode defaultsNode = defaultsLoader.load();
+
+ boolean newFile = false;
2022-06-09 21:51:26 +02:00
+ 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;
+ }
+
2022-06-09 21:51:26 +02:00
+ final YamlConfigurationLoader worldLoader = this.createWorldConfigLoaderBuilder(contextMap)
+ .defaultOptions(this.applyObjectMapperFactory(this.createWorldObjectMapperFactoryBuilder(contextMap).build()))
+ .path(worldConfigFile)
+ .build();
+ final ConfigurationNode worldNode = worldLoader.load();
2022-06-10 07:03:37 +02:00
+ if (newFile) { // set the version field if new file
+ worldNode.node(Configuration.VERSION_FIELD).set(this.worldConfigVersion());
+ } else {
+ this.verifyWorldConfigVersion(contextMap, worldNode);
+ }
+ this.applyWorldConfigTransformations(contextMap, worldNode, defaultsNode);
2022-06-09 21:51:26 +02:00
+ this.applyDefaultsAwareWorldConfigTransformations(contextMap, worldNode, defaultsNode);
+ this.trySaveFileNode(worldLoader, worldNode, worldConfigFile.toString()); // 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);
2022-06-09 21:51:26 +02:00
+ return creator.apply(worldNode);
+ }
+
+ protected void verifyWorldConfigVersion(final ContextMap contextMap, final ConfigurationNode worldNode) {
+ final ConfigurationNode version = worldNode.node(Configuration.VERSION_FIELD);
+ final String worldName = contextMap.require(WORLD_NAME);
+ if (version.virtual()) {
+ if (worldName.equals(WORLD_DEFAULTS)) {
+ LOGGER.warn("The world defaults config file didn't have a version set, assuming latest");
+ } else {
+ LOGGER.warn("The world config file for " + worldName + " didn't have a version set, assuming latest");
+ }
+ version.raw(this.worldConfigVersion());
+ } else if (version.getInt() > this.worldConfigVersion()) {
+ String msg = "Loading a newer configuration than is supported ({} > {})! ";
+ if (worldName.equals(WORLD_DEFAULTS)) {
+ msg += "You may have to backup & delete the world defaults config file to start the server.";
+ } else {
+ msg += "You may have to backup & delete the " + worldName + " config file to start the server.";
+ }
+ LOGGER.error(msg, version.getInt(), this.worldConfigVersion());
+ }
+ }
+
+ protected void applyWorldConfigTransformations(final ContextMap contextMap, final ConfigurationNode node, final @Nullable ConfigurationNode defaultsNode) throws ConfigurateException {
2022-06-09 21:51:26 +02:00
+ }
+
+ 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);
+ }
2022-06-09 21:51:26 +02:00
+
+ 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 static final ContextKey<RegistryAccess> REGISTRY_ACCESS = new ContextKey<>(RegistryAccess.class, "registry access");
+ public static final ContextKey<GameRules> GAME_RULES = new ContextKey<>(GameRules.class, "game rules");
2022-06-09 21:51:26 +02:00
+
+ 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
2024-10-27 18:11:15 +01:00
index 0000000000000000000000000000000000000000..f0d470d7770e119f734b9e72021c806d0ea8ecbd
--- /dev/null
+++ b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java
2024-10-27 18:11:15 +01:00
@@ -0,0 +1,330 @@
+package io.papermc.paper.configuration;
+
2023-10-04 03:06:23 +02:00
+import com.mojang.logging.LogUtils;
+import io.papermc.paper.configuration.constraint.Constraints;
+import io.papermc.paper.configuration.type.number.DoubleOr;
2023-10-04 03:06:23 +02:00
+import io.papermc.paper.configuration.type.number.IntOr;
2022-06-09 23:11:43 +02:00
+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;
2023-10-04 03:06:23 +02:00
+import org.slf4j.Logger;
+import org.spongepowered.configurate.objectmapping.ConfigSerializable;
+import org.spongepowered.configurate.objectmapping.meta.Comment;
2023-10-04 03:06:23 +02:00
+import org.spongepowered.configurate.objectmapping.meta.PostProcess;
+import org.spongepowered.configurate.objectmapping.meta.Required;
+import org.spongepowered.configurate.objectmapping.meta.Setting;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.OptionalInt;
+
+@SuppressWarnings({"CanBeFinal", "FieldCanBeLocal", "FieldMayBeFinal", "NotNullFieldNotInitialized", "InnerClassMayBeStatic"})
+public class GlobalConfiguration extends ConfigurationPart {
2023-10-04 03:06:23 +02:00
+ private static final Logger LOGGER = LogUtils.getLogger();
+ static final int CURRENT_VERSION = 29; // (when you change the version, change the comment, so it conflicts on rebases): <insert changes here>
+ private static GlobalConfiguration instance;
+ public static GlobalConfiguration get() {
+ return instance;
+ }
2024-10-25 21:24:15 +02:00
+
+ public ChunkLoadingBasic chunkLoadingBasic;
+
+ public class ChunkLoadingBasic extends ConfigurationPart {
+ @Comment("The maximum rate in chunks per second that the server will send to any individual player. Set to -1 to disable this limit.")
+ public double playerMaxChunkSendRate = 75.0;
+
+ @Comment(
+ "The maximum rate at which chunks will load for any individual player. " +
+ "Note that this setting also affects chunk generations, since a chunk load is always first issued to test if a" +
+ "chunk is already generated. Set to -1 to disable this limit."
+ )
+ public double playerMaxChunkLoadRate = 100.0;
+
+ @Comment("The maximum rate at which chunks will generate for any individual player. Set to -1 to disable this limit.")
+ public double playerMaxChunkGenerateRate = -1.0;
+ }
+
+ public ChunkLoadingAdvanced chunkLoadingAdvanced;
+
+ public class ChunkLoadingAdvanced extends ConfigurationPart {
+ @Comment(
+ "Set to true if the server will match the chunk send radius that clients have configured" +
+ "in their view distance settings if the client is less-than the server's send distance."
+ )
+ public boolean autoConfigSendDistance = true;
+
+ @Comment(
+ "Specifies the maximum amount of concurrent chunk loads that an individual player can have." +
+ "Set to 0 to let the server configure it automatically per player, or set it to -1 to disable the limit."
+ )
+ public int playerMaxConcurrentChunkLoads = 0;
+
+ @Comment(
+ "Specifies the maximum amount of concurrent chunk generations that an individual player can have." +
+ "Set to 0 to let the server configure it automatically per player, or set it to -1 to disable the limit."
+ )
+ public int playerMaxConcurrentChunkGenerates = 0;
+ }
+ 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 {
2022-06-09 23:11:43 +02:00
+ 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");
+ }
+
2022-06-09 23:11:43 +02:00
+ 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;
+ }
+
2024-07-20 18:35:39 +02:00
+ public Spark spark;
+
+ public class Spark extends ConfigurationPart {
+ public boolean enabled = true;
+ public boolean enableImmediately = false;
+ }
+
+ public Proxies proxies;
+
+ public class Proxies extends ConfigurationPart {
+ public BungeeCord bungeeCord;
+
+ public class BungeeCord extends ConfigurationPart {
+ public boolean onlineMode = true;
+ }
+
+ public Velocity velocity;
+
+ public class Velocity extends ConfigurationPart {
+ public boolean enabled = false;
+ public boolean onlineMode = true;
+ public String secret = "";
2023-10-04 03:06:23 +02:00
+
+ @PostProcess
+ private void postProcess() {
+ if (!this.enabled) return;
+
+ final String environmentSourcedVelocitySecret = System.getenv("PAPER_VELOCITY_SECRET");
+ if (environmentSourcedVelocitySecret != null && !environmentSourcedVelocitySecret.isEmpty()) {
+ this.secret = environmentSourcedVelocitySecret;
+ }
+
+ if (this.secret.isEmpty()) {
2023-10-04 03:06:23 +02:00
+ LOGGER.error("Velocity is enabled, but no secret key was specified. A secret key is required. Disabling velocity...");
+ this.enabled = false;
+ }
+ }
+ }
+ 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 UnsupportedSettings unsupportedSettings;
+
+ public class UnsupportedSettings extends ConfigurationPart {
+ @Comment("This setting controls if the broken behavior of disarmed tripwires not breaking should be allowed. This also allows for dupes")
+ public boolean allowTripwireDisarmingExploits = false;
+ @Comment("This setting allows for exploits related to end portals, for example sand duping")
+ public boolean allowUnsafeEndPortalTeleportation = false;
+ @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;
+ @Comment("This setting controls if the vanilla damage tick should be skipped if damage was blocked via a shield.")
+ public boolean skipVanillaDamageTickWhenShieldBlocked = false;
+ @Comment("This setting controls what compression format is used for region files.")
+ public CompressionFormat compressionFormat = CompressionFormat.ZLIB;
+
+ public enum CompressionFormat {
+ GZIP,
+ ZLIB,
+ NONE
+ }
+ }
+
+ 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 deobfuscateStacktraces = true;
+ }
+
+ public Scoreboards scoreboards;
+
+ public class Scoreboards extends ConfigurationPart {
+ public boolean trackPluginScoreboards = false;
+ public boolean saveEmptyScoreboardTeams = true;
+ }
+
2023-10-04 03:06:23 +02:00
+ @SuppressWarnings("unused") // used in postProcess
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
+ public ChunkSystem chunkSystem;
+
2023-10-04 03:06:23 +02:00
+ public class ChunkSystem extends ConfigurationPart {
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
+
+ public int ioThreads = -1;
+ public int workerThreads = -1;
+ public String genParallelism = "default";
+
2023-10-04 03:06:23 +02:00
+ @PostProcess
+ private void postProcess() {
2024-10-25 21:24:15 +02:00
+
+ }
+ }
+
+ 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 IntOr.Disabled pageMax = new IntOr.Disabled(OptionalInt.of(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 {
2022-06-09 23:11:43 +02:00
+ 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
2022-06-14 00:29:44 +02:00
+ 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 {
2023-10-04 03:06:23 +02:00
+
+ @SuppressWarnings("unused") // used in postProcess
+ public ChatThreads chatThreads;
+ public class ChatThreads extends ConfigurationPart {
2022-09-20 02:16:23 +02:00
+ private int chatExecutorCoreSize = -1;
+ private int chatExecutorMaxSize = -1;
+
2023-10-04 03:06:23 +02:00
+ @PostProcess
+ private void postProcess() {
+ // TODO: fill in separate patch
2022-09-20 02:16:23 +02:00
+ }
+ }
2022-12-21 22:55:42 +01:00
+ public int maxJoinsPerTick = 5;
+ 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 useDimensionTypeForCustomSpawners = false;
+ public boolean strictAdvancementDimensionCheck = false;
+ public IntOr.Default compressionLevel = IntOr.Default.USE_DEFAULT;
+ @Comment("Defines the leniency distance added on the server to the interaction range of a player when validating interact packets.")
+ public DoubleOr.Default clientInteractionLeniencyDistance = DoubleOr.Default.USE_DEFAULT;
+ }
+
+ public BlockUpdates blockUpdates;
+
+ public class BlockUpdates extends ConfigurationPart {
+ public boolean disableNoteblockUpdates = false;
+ public boolean disableTripwireUpdates = false;
+ public boolean disableChorusPlantUpdates = false;
+ public boolean disableMushroomBlockUpdates = false;
+ }
+}
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..1029b6de6f36b08bf634b4056ef5701383f6f258
--- /dev/null
+++ b/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java
@@ -0,0 +1,467 @@
+package io.papermc.paper.configuration;
+
2022-06-09 21:51:26 +02:00
+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;
2023-10-04 03:06:23 +02:00
+import io.papermc.paper.configuration.mapping.InnerClassFieldDiscoverer;
+import io.papermc.paper.configuration.serializer.ComponentSerializer;
+import io.papermc.paper.configuration.serializer.EnumValueSerializer;
+import io.papermc.paper.configuration.serializer.NbtPathSerializer;
+import io.papermc.paper.configuration.serializer.PacketClassSerializer;
+import io.papermc.paper.configuration.serializer.StringRepresentableSerializer;
2023-10-04 03:06:23 +02:00
+import io.papermc.paper.configuration.serializer.collections.FastutilMapSerializer;
+import io.papermc.paper.configuration.serializer.collections.MapSerializer;
2023-10-04 03:06:23 +02:00
+import io.papermc.paper.configuration.serializer.collections.TableSerializer;
+import io.papermc.paper.configuration.serializer.registry.RegistryHolderSerializer;
+import io.papermc.paper.configuration.serializer.registry.RegistryValueSerializer;
2022-06-09 21:51:26 +02:00
+import io.papermc.paper.configuration.transformation.Transformations;
+import io.papermc.paper.configuration.transformation.global.LegacyPaperConfig;
+import io.papermc.paper.configuration.transformation.global.versioned.V29_LogIPs;
2022-06-09 21:51:26 +02:00
+import io.papermc.paper.configuration.transformation.world.FeatureSeedsGeneration;
+import io.papermc.paper.configuration.transformation.world.LegacyPaperWorldConfig;
+import io.papermc.paper.configuration.transformation.world.versioned.V29_ZeroWorldHeight;
+import io.papermc.paper.configuration.transformation.world.versioned.V30_RenameFilterNbtFromSpawnEgg;
+import io.papermc.paper.configuration.transformation.world.versioned.V31_SpawnLoadedRangeToGameRule;
+import io.papermc.paper.configuration.type.BooleanOrDefault;
+import io.papermc.paper.configuration.type.DespawnRange;
+import io.papermc.paper.configuration.type.Duration;
+import io.papermc.paper.configuration.type.DurationOrDisabled;
2022-07-18 12:30:31 +02:00
+import io.papermc.paper.configuration.type.EngineMode;
+import io.papermc.paper.configuration.type.fallback.FallbackValueSerializer;
2023-10-04 03:06:23 +02:00
+import io.papermc.paper.configuration.type.number.DoubleOr;
+import io.papermc.paper.configuration.type.number.IntOr;
+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;
2023-10-04 03:06:23 +02:00
+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.List;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import net.minecraft.core.RegistryAccess;
+import net.minecraft.core.registries.Registries;
2022-06-09 21:51:26 +02:00
+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.GameRules;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
2022-06-09 21:51:26 +02:00
+import org.apache.commons.lang3.RandomStringUtils;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jetbrains.annotations.VisibleForTesting;
+import org.slf4j.Logger;
2022-06-09 21:51:26 +02:00
+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;
2022-06-09 22:48:35 +02:00
+import org.spongepowered.configurate.NodePath;
+import org.spongepowered.configurate.objectmapping.ObjectMapper;
2022-06-09 21:51:26 +02:00
+import org.spongepowered.configurate.transformation.ConfigurationTransformation;
2022-06-09 22:48:35 +02:00
+import org.spongepowered.configurate.transformation.TransformAction;
+import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
+
+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.getClassLogger();
+ 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";
2022-06-10 19:01:09 +02:00
+ public static final String CONFIG_DIR = "config";
+ private static final String BACKUP_DIR ="legacy-backup";
+
2022-06-11 22:43:59 +02:00
+ private static final String GLOBAL_HEADER = String.format("""
+ This is the global configuration file for Paper.
2022-06-09 13:16:00 +02:00
+ 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,
2022-06-09 13:16:00 +02:00
+ join us in our Discord or check the docs page.
+
2022-06-11 22:43:59 +02:00
+ The world configuration options have been moved inside
+ their respective world folder. The files are named %s
+
2022-06-11 22:43:59 +02:00
+ Docs: https://docs.papermc.io/
+ Discord: https://discord.gg/papermc
2022-06-11 22:43:59 +02:00
+ Website: https://papermc.io/""", WORLD_CONFIG_FILE_NAME);
+
+ private static final String WORLD_DEFAULTS_HEADER = """
+ This is the world defaults configuration file for Paper.
2022-06-09 13:16:00 +02:00
+ 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,
2022-06-09 13:16:00 +02:00
+ 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.
+
2022-06-11 22:43:59 +02:00
+ Docs: https://docs.papermc.io/
+ Discord: https://discord.gg/papermc
2022-06-11 22:43:59 +02:00
+ 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 String MOVED_NOTICE = """
+ The global and world default configuration files have moved to %s
+ and the world-specific configuration file has been moved inside
+ the respective world folder.
+
+ See https://docs.papermc.io/paper/configuration for more information.
+ """;
+
+ @VisibleForTesting
+ public static final Supplier<SpigotWorldConfig> SPIGOT_WORLD_DEFAULTS = Suppliers.memoize(() -> new SpigotWorldConfig(RandomStringUtils.randomAlphabetic(255)) {
2022-06-09 21:51:26 +02:00
+ @Override // override to ensure "verbose" is false
+ public void init() {
+ SpigotConfig.readConfig(SpigotWorldConfig.class, this);
+ }
+ });
2023-10-04 03:06:23 +02:00
+ public static final ContextKey<Supplier<SpigotWorldConfig>> SPIGOT_WORLD_CONFIG_CONTEXT_KEY = new ContextKey<>(new TypeToken<Supplier<SpigotWorldConfig>>() {}, "spigot world config");
2022-06-09 21:51:26 +02:00
+
+
+ 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 int globalConfigVersion() {
+ return GlobalConfiguration.CURRENT_VERSION;
+ }
+
+ @Override
+ protected int worldConfigVersion() {
+ return WorldConfiguration.CURRENT_VERSION;
+ }
+
+ @Override
+ protected YamlConfigurationLoader.Builder createLoaderBuilder() {
+ return super.createLoaderBuilder()
+ .defaultOptions(PaperConfigurations::defaultOptions);
+ }
+
+ private static ConfigurationOptions defaultOptions(ConfigurationOptions options) {
2022-06-09 21:51:26 +02:00
+ return options.serializers(builder -> builder
+ .register(MapSerializer.TYPE, new MapSerializer(false))
+ .register(new EnumValueSerializer())
2022-06-09 23:11:43 +02:00
+ .register(new ComponentSerializer())
+ .register(IntOr.Default.SERIALIZER)
+ .register(IntOr.Disabled.SERIALIZER)
+ .register(DoubleOr.Default.SERIALIZER)
+ .register(DoubleOr.Disabled.SERIALIZER)
+ .register(BooleanOrDefault.SERIALIZER)
+ .register(Duration.SERIALIZER)
+ .register(DurationOrDisabled.SERIALIZER)
+ .register(NbtPathSerializer.SERIALIZER)
2022-06-09 21:51:26 +02:00
+ );
+ }
+
+ @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(final RegistryAccess registryAccess) throws ConfigurateException {
+ GlobalConfiguration configuration = super.initializeGlobalConfiguration(registryAccess);
+ GlobalConfiguration.set(configuration);
+ return configuration;
+ }
+
+ @Override
+ protected ContextMap.Builder createDefaultContextMap(final RegistryAccess registryAccess) {
+ return super.createDefaultContextMap(registryAccess)
2022-06-09 21:51:26 +02:00
+ .put(SPIGOT_WORLD_CONFIG_CONTEXT_KEY, SPIGOT_WORLD_DEFAULTS);
+ }
+
+ @Override
2022-06-09 21:51:26 +02:00
+ 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())
2023-10-04 03:06:23 +02:00
+ .addDiscoverer(InnerClassFieldDiscoverer.worldConfig(createWorldConfigInstance(contextMap)));
+ }
+
+ private static WorldConfiguration createWorldConfigInstance(ContextMap contextMap) {
+ return new WorldConfiguration(
+ contextMap.require(PaperConfigurations.SPIGOT_WORLD_CONFIG_CONTEXT_KEY).get(),
+ contextMap.require(Configurations.WORLD_KEY)
+ );
+ }
+
+ @Override
2022-06-09 21:51:26 +02:00
+ protected YamlConfigurationLoader.Builder createWorldConfigLoaderBuilder(final ContextMap contextMap) {
+ final RegistryAccess access = contextMap.require(REGISTRY_ACCESS);
2022-06-09 21:51:26 +02:00
+ return super.createWorldConfigLoaderBuilder(contextMap)
+ .defaultOptions(options -> options
2022-06-11 22:43:59 +02:00
+ .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(DespawnRange.class, DespawnRange.SERIALIZER)
2022-12-08 09:52:08 +01:00
+ .register(StringRepresentableSerializer::isValidFor, new StringRepresentableSerializer())
2022-07-18 12:30:31 +02:00
+ .register(EngineMode.SERIALIZER)
2022-06-09 21:51:26 +02:00
+ .register(FallbackValueSerializer.create(contextMap.require(SPIGOT_WORLD_CONFIG_CONTEXT_KEY).get(), MinecraftServer::getServer))
+ .register(new RegistryValueSerializer<>(new TypeToken<EntityType<?>>() {}, access, Registries.ENTITY_TYPE, true))
+ .register(new RegistryValueSerializer<>(Item.class, access, Registries.ITEM, true))
+ .register(new RegistryValueSerializer<>(Block.class, access, Registries.BLOCK, true))
+ .register(new RegistryHolderSerializer<>(new TypeToken<ConfiguredFeature<?, ?>>() {}, access, Registries.CONFIGURED_FEATURE, false))
+ )
+ );
+ }
+
+ @Override
+ protected void applyWorldConfigTransformations(final ContextMap contextMap, final ConfigurationNode node, final @Nullable ConfigurationNode defaultsNode) throws ConfigurateException {
+ final ConfigurationTransformation.Builder builder = ConfigurationTransformation.builder();
+ for (final NodePath path : RemovedConfigurations.REMOVED_WORLD_PATHS) {
2022-06-09 22:48:35 +02:00
+ builder.addAction(path, TransformAction.remove());
+ }
+ builder.build().apply(node);
+
+ final ConfigurationTransformation.VersionedBuilder versionedBuilder = Transformations.versionedBuilder();
+ V29_ZeroWorldHeight.apply(versionedBuilder);
+ V30_RenameFilterNbtFromSpawnEgg.apply(versionedBuilder);
+ V31_SpawnLoadedRangeToGameRule.apply(versionedBuilder, contextMap, defaultsNode);
+ // ADD FUTURE VERSIONED TRANSFORMS TO versionedBuilder HERE
+ versionedBuilder.build().apply(node);
2022-06-09 22:48:35 +02:00
+ }
+
+ @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);
+
+ final ConfigurationTransformation.VersionedBuilder versionedBuilder = Transformations.versionedBuilder();
+ V29_LogIPs.apply(versionedBuilder);
+ // ADD FUTURE VERSIONED TRANSFORMS TO versionedBuilder HERE
+ versionedBuilder.build().apply(node);
+ }
+
+ private static final List<Transformations.DefaultsAware> DEFAULT_AWARE_TRANSFORMATIONS = List.of(
+ FeatureSeedsGeneration::apply
+ );
2022-06-09 21:51:26 +02:00
+
+ @Override
2022-06-09 21:51:26 +02:00
+ 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));
+ builder.build().apply(worldNode);
2022-06-09 21:51:26 +02:00
+ }
+
+ @Override
+ public WorldConfiguration createWorldConfig(final ContextMap contextMap) {
+ final String levelName = contextMap.require(WORLD_NAME);
+ try {
2022-06-09 21:51:26 +02:00
+ 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(server.registryAccess());
+ for (ServerLevel level : server.getAllLevels()) {
2022-06-09 21:51:26 +02:00
+ this.createWorldConfig(createWorldContextMap(level), reloader(this.worldConfigClass, level.paperConfig()));
+ }
+ } catch (Exception ex) {
+ throw new RuntimeException("Could not reload paper configuration files", ex);
+ }
+ }
+
2022-06-09 21:51:26 +02:00
+ private static ContextMap createWorldContextMap(ServerLevel level) {
+ return createWorldContextMap(level.convertable.levelDirectory.path(), level.serverLevelData.getLevelName(), level.dimension().location(), level.spigotConfig, level.registryAccess(), level.getGameRules());
2022-06-09 21:51:26 +02:00
+ }
+
+ public static ContextMap createWorldContextMap(final Path dir, final String levelName, final ResourceLocation worldKey, final SpigotWorldConfig spigotConfig, final RegistryAccess registryAccess, final GameRules gameRules) {
2022-06-09 21:51:26 +02:00
+ return ContextMap.builder()
+ .put(WORLD_DIRECTORY, dir)
+ .put(WORLD_NAME, levelName)
+ .put(WORLD_KEY, worldKey)
+ .put(SPIGOT_WORLD_CONFIG_CONTEXT_KEY, Suppliers.ofInstance(spigotConfig))
+ .put(REGISTRY_ACCESS, registryAccess)
+ .put(GAME_RULES, gameRules)
2022-06-09 21:51:26 +02:00
+ .build();
+ }
+
+ public static PaperConfigurations setup(final Path legacyConfig, final Path configDir, final Path worldFolder, final File spigotConfig) throws Exception {
+ final Path legacy = Files.isSymbolicLink(legacyConfig) ? Files.readSymbolicLink(legacyConfig) : legacyConfig;
+ if (needsConverting(legacyConfig)) {
+ final String legacyFileName = legacyConfig.getFileName().toString();
+ try {
2022-06-10 19:01:09 +02:00
+ if (Files.exists(configDir) && !Files.isDirectory(configDir)) {
+ throw new RuntimeException("Paper needs to create a '" + configDir.toAbsolutePath() + "' folder. You already have a non-directory named '" + configDir.toAbsolutePath() + "'. Please remove it and restart the server.");
2022-06-10 19:01:09 +02:00
+ }
+ final Path backupDir = configDir.resolve(BACKUP_DIR);
+ if (Files.exists(backupDir) && !Files.isDirectory(backupDir)) {
+ throw new RuntimeException("Paper needs to create a '" + BACKUP_DIR + "' directory in the '" + configDir.toAbsolutePath() + "' folder. You already have a non-directory named '" + BACKUP_DIR + "'. Please remove it and restart the server.");
2022-06-10 19:01:09 +02:00
+ }
+ createDirectoriesSymlinkAware(backupDir);
+ final String backupFileName = legacyFileName + ".old";
2022-06-10 19:01:09 +02:00
+ final Path legacyConfigBackup = backupDir.resolve(backupFileName);
+ if (Files.exists(legacyConfigBackup) && !Files.isRegularFile(legacyConfigBackup)) {
+ throw new RuntimeException("Paper needs to create a '" + backupFileName + "' file in the '" + backupDir.toAbsolutePath() + "' folder. You already have a non-file named '" + backupFileName + "'. Please remove it and restart the server.");
2022-06-10 19:01:09 +02:00
+ }
+ Files.move(legacyConfig.toRealPath(), legacyConfigBackup, StandardCopyOption.REPLACE_EXISTING); // make backup
+ if (Files.isSymbolicLink(legacyConfig)) {
+ Files.delete(legacyConfig);
+ }
+ final Path replacementFile = legacy.resolveSibling(legacyFileName + "-README.txt");
+ if (Files.notExists(replacementFile)) {
+ Files.createFile(replacementFile);
+ Files.writeString(replacementFile, String.format(MOVED_NOTICE, configDir.toAbsolutePath()));
+ }
+ convert(legacyConfigBackup, configDir, worldFolder, spigotConfig);
+ } catch (final IOException ex) {
+ throw new RuntimeException("Could not convert '" + legacyFileName + "' to the new configuration format", ex);
+ }
+ }
+ 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);
+ }
+
+ // Symlinks are not correctly checked in createDirectories
+ static void createDirectoriesSymlinkAware(Path path) throws IOException {
+ if (!Files.isDirectory(path)) {
+ Files.createDirectories(path);
+ }
+ }
+}
2022-06-09 22:48:35 +02:00
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
2024-10-23 15:02:51 +02:00
index 0000000000000000000000000000000000000000..279b24c689b9979884b65df7eb1f059024f0feac
2022-06-09 22:48:35 +02:00
--- /dev/null
+++ b/src/main/java/io/papermc/paper/configuration/RemovedConfigurations.java
2024-10-23 15:02:51 +02:00
@@ -0,0 +1,83 @@
2022-06-09 22:48:35 +02:00
+package io.papermc.paper.configuration;
+
+import org.spongepowered.configurate.NodePath;
+
+import static org.spongepowered.configurate.NodePath.path;
+
+interface RemovedConfigurations {
+
+ NodePath[] REMOVED_WORLD_PATHS = {
+ path("falling-blocks-collide-with-signs"),
+ path("fast-drain"),
+ path("lava-flow-speed"),
+ path("load-chunks"),
+ path("misc", "boats-drop-boats"),
+ path("player-exhaustion"),
+ path("remove-unloaded"),
+ path("tick-next-tick-list-cap"),
+ path("tick-next-tick-list-cap-ignores-redstone"),
2022-06-09 22:48:35 +02:00
+ 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"),
2022-06-10 00:15:01 +02:00
+ path("allow-undead-horse-leashing"),
2022-06-09 22:48:35 +02:00
+ 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"),
2022-06-09 22:48:35 +02:00
+ 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"),
2022-06-10 00:15:01 +02:00
+ 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"),
2022-06-14 00:29:44 +02:00
+ path("entities", "spawning", "despawn-ranges", "soft"),
+ path("entities", "spawning", "despawn-ranges", "hard"),
+ path("fixes", "fix-curing-zombie-villager-discount-exploit"),
2024-10-23 15:02:51 +02:00
+ path("entities", "mob-effects", "undead-immune-to-certain-effects"),
+ path("entities", "entities-target-with-follow-range")
2022-06-09 22:48:35 +02:00
+ };
+ // spawn.keep-spawn-loaded and spawn.keep-spawn-loaded-range are no longer used, but kept
+ // in the world default config for compatibility with old worlds being migrated to use the gamerule
2022-06-09 22:48:35 +02:00
+
+ NodePath[] REMOVED_GLOBAL_PATHS = {
+ path("data-value-allowed-items"),
+ path("effect-modifiers"),
+ path("stackable-buckets"),
+ path("async-chunks"),
2022-06-09 22:48:35 +02:00
+ 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
2022-06-10 00:15:01 +02:00
+ path("item-validation", "loc-name"),
+ path("commandErrorMessage"),
+ path("baby-zombie-movement-speed"),
+ path("limit-player-interactions"),
+ path("warnWhenSettingExcessiveVelocity"),
+ path("logging", "use-rgb-for-named-text-colors"),
+ path("unsupported-settings", "allow-grindstone-overstacking")
2022-06-09 22:48:35 +02:00
+ };
+
+}
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..dad3fcc689ec806f985122a7cbd501a7d0fd0d36
--- /dev/null
+++ b/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java
@@ -0,0 +1,581 @@
+package io.papermc.paper.configuration;
+
+import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.Table;
2022-06-09 13:16:00 +02:00
+import com.mojang.logging.LogUtils;
+import io.papermc.paper.configuration.legacy.MaxEntityCollisionsInitializer;
+import io.papermc.paper.configuration.legacy.RequiresSpigotInitialization;
2023-10-04 03:06:23 +02:00
+import io.papermc.paper.configuration.mapping.MergeMap;
+import io.papermc.paper.configuration.serializer.NbtPathSerializer;
2022-06-09 21:51:26 +02:00
+import io.papermc.paper.configuration.transformation.world.FeatureSeedsGeneration;
+import io.papermc.paper.configuration.type.BooleanOrDefault;
+import io.papermc.paper.configuration.type.DespawnRange;
+import io.papermc.paper.configuration.type.Duration;
+import io.papermc.paper.configuration.type.DurationOrDisabled;
2022-07-18 12:30:31 +02:00
+import io.papermc.paper.configuration.type.EngineMode;
+import io.papermc.paper.configuration.type.fallback.ArrowDespawnRate;
+import io.papermc.paper.configuration.type.fallback.AutosavePeriod;
2023-10-04 03:06:23 +02:00
+import io.papermc.paper.configuration.type.number.BelowZeroToEmpty;
+import io.papermc.paper.configuration.type.number.DoubleOr;
+import io.papermc.paper.configuration.type.number.IntOr;
+import 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 java.util.Arrays;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.OptionalDouble;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import net.minecraft.Util;
+import net.minecraft.commands.arguments.NbtPathArgument;
+import net.minecraft.core.Holder;
+import net.minecraft.core.registries.BuiltInRegistries;
2022-06-09 21:51:26 +02:00
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.world.Difficulty;
+import net.minecraft.world.entity.Display;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
2023-10-03 14:19:39 +02:00
+import net.minecraft.world.entity.ExperienceOrb;
+import net.minecraft.world.entity.MobCategory;
2023-10-03 14:19:39 +02:00
+import net.minecraft.world.entity.boss.enderdragon.EnderDragon;
+import net.minecraft.world.entity.decoration.HangingEntity;
+import net.minecraft.world.entity.item.ItemEntity;
+import net.minecraft.world.entity.monster.Vindicator;
+import net.minecraft.world.entity.monster.Zombie;
2023-10-03 14:19:39 +02:00
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.item.Item;
+import net.minecraft.world.item.Items;
+import net.minecraft.world.level.NaturalSpawner;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.block.Blocks;
+import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
2022-06-09 13:16:00 +02:00
+import org.slf4j.Logger;
+import org.spigotmc.SpigotWorldConfig;
+import org.spongepowered.configurate.objectmapping.ConfigSerializable;
+import org.spongepowered.configurate.objectmapping.meta.Comment;
2023-10-04 03:06:23 +02:00
+import org.spongepowered.configurate.objectmapping.meta.PostProcess;
+import org.spongepowered.configurate.objectmapping.meta.Required;
+import org.spongepowered.configurate.objectmapping.meta.Setting;
+import org.spongepowered.configurate.serialize.SerializationException;
+
+@SuppressWarnings({"FieldCanBeLocal", "FieldMayBeFinal", "NotNullFieldNotInitialized", "InnerClassMayBeStatic"})
+public class WorldConfiguration extends ConfigurationPart {
+ private static final Logger LOGGER = LogUtils.getClassLogger();
+ static final int CURRENT_VERSION = 31; // (when you change the version, change the comment, so it conflicts on rebases): migrate spawn loaded configs to gamerule
+
+ private final transient SpigotWorldConfig spigotConfig;
+ private final transient ResourceLocation worldKey;
+
+ WorldConfiguration(final SpigotWorldConfig spigotConfig, final ResourceLocation worldKey) {
+ this.spigotConfig = spigotConfig;
2022-06-09 21:51:26 +02:00
+ this.worldKey = worldKey;
+ }
+
+ public boolean isDefault() {
+ return this.worldKey.equals(PaperConfigurations.WORLD_DEFAULTS_KEY);
+ }
+
+ @Setting(Configuration.VERSION_FIELD)
+ public int version = CURRENT_VERSION;
+
2022-07-18 12:30:31 +02:00
+ public Anticheat anticheat;
+
2022-07-18 12:30:31 +02:00
+ 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 boolean hideItemmetaWithVisualEffects = false;
+ }
+ }
+
2022-07-18 12:30:31 +02:00
+ public AntiXray antiXray;
+
2022-07-18 12:30:31 +02:00
+ public class AntiXray extends ConfigurationPart {
+ public boolean enabled = false;
2022-07-18 12:30:31 +02:00
+ public EngineMode engineMode = EngineMode.HIDE;
+ public int maxBlockHeight = 64;
+ public int updateRadius = 2;
+ public boolean lavaObscures = false;
+ public boolean usePermission = false;
+ public List<Block> hiddenBlocks = List.of(
+ //<editor-fold desc="Anti-Xray Hidden Blocks" defaultstate="collapsed">
+ Blocks.COPPER_ORE,
+ Blocks.DEEPSLATE_COPPER_ORE,
+ Blocks.RAW_COPPER_BLOCK,
+ Blocks.GOLD_ORE,
+ Blocks.DEEPSLATE_GOLD_ORE,
+ Blocks.IRON_ORE,
+ Blocks.DEEPSLATE_IRON_ORE,
+ Blocks.RAW_IRON_BLOCK,
+ Blocks.COAL_ORE,
+ Blocks.DEEPSLATE_COAL_ORE,
+ Blocks.LAPIS_ORE,
+ Blocks.DEEPSLATE_LAPIS_ORE,
+ Blocks.MOSSY_COBBLESTONE,
+ Blocks.OBSIDIAN,
+ Blocks.CHEST,
+ Blocks.DIAMOND_ORE,
+ Blocks.DEEPSLATE_DIAMOND_ORE,
+ Blocks.REDSTONE_ORE,
+ Blocks.DEEPSLATE_REDSTONE_ORE,
+ Blocks.CLAY,
+ Blocks.EMERALD_ORE,
+ Blocks.DEEPSLATE_EMERALD_ORE,
+ Blocks.ENDER_CHEST
+ //</editor-fold>
+ );
+ public List<Block> replacementBlocks = List.of(Blocks.STONE, Blocks.OAK_PLANKS, Blocks.DEEPSLATE);
+ }
+ }
+
+ public Entities entities;
+
+ public class Entities extends ConfigurationPart {
+ public MobEffects mobEffects;
+
+ public class MobEffects extends ConfigurationPart {
+ 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 Markers markers;
+
+ public class Markers extends ConfigurationPart {
+ public boolean tick = true;
+ }
+
2023-08-26 22:12:46 +02:00
+ public Sniffer sniffer;
+
+ public class Sniffer extends ConfigurationPart {
+ public IntOr.Default hatchTime = IntOr.Default.USE_DEFAULT;
+ public IntOr.Default boostedHatchTime = IntOr.Default.USE_DEFAULT;
+ }
+
+ 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 filterBadTileEntityNbtFromFallingBlocks = true;
+ public List<NbtPathArgument.NbtPath> filteredEntityTagNbtPaths = NbtPathSerializer.fromString(List.of("Pos", "Motion", "SleepingX", "SleepingY", "SleepingZ"));
+ 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, DespawnRangePair> despawnRanges = Arrays.stream(MobCategory.values()).collect(Collectors.toMap(Function.identity(), category -> DespawnRangePair.createDefault()));
+ public DespawnRange.Shape despawnRangeShape = DespawnRange.Shape.ELLIPSOID;
+ @MergeMap
+ public Reference2IntMap<MobCategory> ticksPerSpawn = Util.make(new Reference2IntOpenHashMap<>(NaturalSpawner.SPAWNING_CATEGORIES.length), map -> Arrays.stream(NaturalSpawner.SPAWNING_CATEGORIES).forEach(mobCategory -> map.put(mobCategory, -1)));
+
+ @ConfigSerializable
+ public record DespawnRangePair(@Required DespawnRange hard, @Required DespawnRange soft) {
+ public static DespawnRangePair createDefault() {
+ return new DespawnRangePair(
+ new DespawnRange(IntOr.Default.USE_DEFAULT),
+ new DespawnRange(IntOr.Default.USE_DEFAULT)
+ );
+ }
+ }
+
+ @PostProcess
+ public void precomputeDespawnDistances() throws SerializationException {
+ for (Map.Entry<MobCategory, DespawnRangePair> entry : this.despawnRanges.entrySet()) {
+ final MobCategory category = entry.getKey();
+ final DespawnRangePair range = entry.getValue();
+ range.hard().preComputed(category.getDespawnDistance(), category.getSerializedName());
+ range.soft().preComputed(category.getNoDespawnDistance(), category.getSerializedName());
+ }
+ }
+
+ public WaterAnimalSpawnHeight wateranimalSpawnHeight;
+
+ public class WaterAnimalSpawnHeight extends ConfigurationPart {
+ public IntOr.Default maximum = IntOr.Default.USE_DEFAULT;
+ public IntOr.Default minimum = IntOr.Default.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;
2023-10-04 03:06:23 +02:00
+ @BelowZeroToEmpty
+ public DoubleOr.Default skeletonHorseThunderSpawnChance = DoubleOr.Default.USE_DEFAULT;
+ public boolean ironGolemsCanSpawnInAir = false;
+ public boolean countAllMobsForSpawning = false;
2023-10-04 03:06:23 +02:00
+ @BelowZeroToEmpty
+ public IntOr.Default monsterSpawnMaxLightLevel = IntOr.Default.USE_DEFAULT;
+ 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 boolean allowSpiderWorldBorderClimbing = true;
+
+ private static final List<EntityType<?>> ZOMBIE_LIKE = List.of(EntityType.ZOMBIE, EntityType.HUSK, EntityType.ZOMBIE_VILLAGER, EntityType.ZOMBIFIED_PIGLIN);
+ @MergeMap
+ public Map<EntityType<?>, List<Difficulty>> doorBreakingDifficulty = Util.make(new IdentityHashMap<>(), map -> {
+ for (final EntityType<?> type : ZOMBIE_LIKE) {
+ map.put(type, Arrays.stream(Difficulty.values()).filter(Zombie.DOOR_BREAKING_PREDICATE).toList());
+ }
+ map.put(EntityType.VINDICATOR, Arrays.stream(Difficulty.values()).filter(Vindicator.DOOR_BREAKING_PREDICATE).toList());
+ });
+
+ public boolean disableCreeperLingeringEffect = false;
+ public boolean enderDragonsDeathAlwaysPlacesDragonEgg = false;
+ public boolean phantomsDoNotSpawnOnCreativePlayers = true;
+ public boolean phantomsOnlyAttackInsomniacs = true;
+ public int playerInsomniaStartTicks = 72000;
+ public int phantomsSpawnAttemptMinSeconds = 60;
+ public int phantomsSpawnAttemptMaxSeconds = 119;
+ public boolean parrotsAreUnaffectedByPlayerMovement = false;
2023-10-04 03:06:23 +02:00
+ @BelowZeroToEmpty
+ public DoubleOr.Default zombieVillagerInfectionChance = DoubleOr.Default.USE_DEFAULT;
+ public MobsCanAlwaysPickUpLoot mobsCanAlwaysPickUpLoot;
+
+ public class MobsCanAlwaysPickUpLoot extends ConfigurationPart {
+ public boolean zombies = false;
+ public boolean skeletons = false;
+ }
+
+ public boolean disablePlayerCrits = false;
+ public boolean nerfPigmenFromNetherPortals = false;
+ @Comment("Prevents merging items that are not on the same y level, preventing potential visual artifacts.")
+ public boolean onlyMergeItemsHorizontally = false;
+ public PillagerPatrols pillagerPatrols;
+
+ 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 TrackingRangeY trackingRangeY;
+
+ public class TrackingRangeY extends ConfigurationPart {
+ public boolean enabled = false;
+ public IntOr.Default player = IntOr.Default.USE_DEFAULT;
+ public IntOr.Default animal = IntOr.Default.USE_DEFAULT;
+ public IntOr.Default monster = IntOr.Default.USE_DEFAULT;
+ public IntOr.Default misc = IntOr.Default.USE_DEFAULT;
+ public IntOr.Default display = IntOr.Default.USE_DEFAULT;
+ public IntOr.Default other = IntOr.Default.USE_DEFAULT;
+
+ public int get(Entity entity, int def) {
2023-10-03 14:19:39 +02:00
+ if (entity instanceof EnderDragon) {
+ return -1; // Ender dragon is exempt
+ } else if (entity instanceof Display) {
+ return display.or(def);
+ } else if (entity instanceof Player) {
+ return player.or(def);
+ } else if (entity instanceof HangingEntity || entity instanceof ItemEntity || entity instanceof ExperienceOrb) {
+ return misc.or(def);
+ }
+ switch (entity.activationType) {
+ case ANIMAL, WATER, VILLAGER -> {
+ return animal.or(def);
+ }
2023-10-03 14:19:39 +02:00
+ case MONSTER, FLYING_MONSTER, RAIDER -> {
+ return monster.or(def);
+ }
2023-10-03 14:19:39 +02:00
+ default -> {
+ return other.or(def);
+ }
+ }
+ }
+ }
+ }
+
+ public Lootables lootables;
+
+ public class Lootables extends ConfigurationPart {
+ public boolean autoReplenish = false;
+ public boolean restrictPlayerReloot = true;
+ public DurationOrDisabled restrictPlayerRelootTime = DurationOrDisabled.USE_DISABLED;
+ public boolean resetSeedOnFill = true;
+ public int maxRefills = -1;
+ public Duration refreshMin = Duration.of("12h");
+ public Duration refreshMax = Duration.of("2d");
+ public boolean retainUnlootedShulkerBoxLootTableOnNonPlayerBreak = true;
+ }
+
+ 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 = true;
+ 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 DoubleOr.Disabled voidDamageAmount = new DoubleOr.Disabled(OptionalDouble.of(4));
+ public double voidDamageMinBuildHeightOffset = -64.0;
+
+ 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;
+ }
+
2022-10-23 00:57:09 +02:00
+ public int fireTickDelay = 30;
+ public int waterOverLavaFlowSpeed = 5;
+ public int portalSearchRadius = 128;
+ public int portalCreateRadius = 16;
+ public boolean portalSearchVanillaDimensionScaling = true;
+ public boolean disableTeleportationSuffocationCheck = false;
+ public IntOr.Disabled netherCeilingVoidDamageHeight = IntOr.Disabled.DISABLED;
+ public int maxFluidTicks = 65536;
+ public int maxBlockTicks = 65536;
+ public boolean locateStructuresOutsideWorldBorder = false;
+ }
+
+ public Spawn spawn;
+
+ public class Spawn extends ConfigurationPart {
+ 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 = false;
+ public boolean preventTntFromMovingInWater = false;
+ public boolean splitOverstackedLoot = true;
+ public IntOr.Disabled fallingBlockHeightNerf = IntOr.Disabled.DISABLED;
+ public IntOr.Disabled tntEntityHeightNerf = IntOr.Disabled.DISABLED;
+ }
+
+ public UnsupportedSettings unsupportedSettings;
+
+ public class UnsupportedSettings extends ConfigurationPart {
+ public boolean fixInvulnerableEndCrystalExploit = true;
2024-03-03 22:10:45 +01:00
+ public boolean disableWorldTickingWhenEmpty = false;
+ }
+
+ 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<>(BuiltInRegistries.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 boolean flushRegionsOnSave = false;
+ }
+
+ 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 int wetFarmland = 1;
+ public int dryFarmland = 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));
+ }
+
2022-06-09 21:51:26 +02:00
+ @Setting(FeatureSeedsGeneration.FEATURE_SEEDS_KEY)
+ public FeatureSeeds featureSeeds;
+
2023-10-04 03:06:23 +02:00
+ public class FeatureSeeds extends ConfigurationPart {
+ @SuppressWarnings("unused") // Is used in FeatureSeedsGeneration
2022-06-09 21:51:26 +02:00
+ @Setting(FeatureSeedsGeneration.GENERATE_KEY)
+ public boolean generateRandomSeedsForAll = false;
2022-06-09 21:51:26 +02:00
+ @Setting(FeatureSeedsGeneration.FEATURES_KEY)
+ public Reference2LongMap<Holder<ConfiguredFeature<?, ?>>> features = new Reference2LongOpenHashMap<>();
2022-06-09 13:16:00 +02:00
+
2023-10-04 03:06:23 +02:00
+ @PostProcess
+ private void postProcess() {
2022-06-09 21:51:26 +02:00
+ this.features.defaultReturnValue(-1);
2022-06-09 13:16:00 +02:00
+ }
+ }
+
+ public CommandBlocks commandBlocks;
+
+ public class CommandBlocks extends ConfigurationPart {
+ public int permissionsLevel = 2;
+ public boolean forceFollowPermLevel = true;
+ }
+
+ 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 AlternateCurrentUpdateOrder alternateCurrentUpdateOrder = AlternateCurrentUpdateOrder.HORIZONTAL_FIRST_OUTWARD;
+ public boolean disableEndCredits = false;
+ public DoubleOr.Default maxLeashDistance = DoubleOr.Default.USE_DEFAULT;
+ public boolean disableSprintInterruptionOnAttack = false;
+ public int shieldBlockingDelay = 5;
+ public boolean disableRelativeProjectileVelocity = false;
+ public boolean legacyEnderPearlBehavior = false;
+
+ public enum RedstoneImplementation {
+ VANILLA, EIGENCRAFT, ALTERNATE_CURRENT
+ }
+
+ public enum AlternateCurrentUpdateOrder {
+ HORIZONTAL_FIRST_OUTWARD, HORIZONTAL_FIRST_INWARD, VERTICAL_FIRST_OUTWARD, VERTICAL_FIRST_INWARD
+ }
+ }
+}
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
2023-10-04 03:06:23 +02:00
index 0000000000000000000000000000000000000000..2d8c91007d5ebc051623bb308cf973bdad3f3273
--- /dev/null
+++ b/src/main/java/io/papermc/paper/configuration/constraint/Constraints.java
2023-10-04 03:06:23 +02:00
@@ -0,0 +1,43 @@
+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.Type;
2023-10-04 03:06:23 +02:00
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.spongepowered.configurate.objectmapping.meta.Constraint;
+import org.spongepowered.configurate.serialize.SerializationException;
+
+public final class Constraints {
+ private Constraints() {
+ }
+
+ 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");
+ }
+ }
+ }
+
+ @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;
+ }
+}
2023-10-04 03:06:23 +02:00
diff --git a/src/main/java/io/papermc/paper/configuration/mapping/InnerClassFieldDiscoverer.java b/src/main/java/io/papermc/paper/configuration/mapping/InnerClassFieldDiscoverer.java
2022-06-09 23:11:43 +02:00
new file mode 100644
2023-10-04 03:06:23 +02:00
index 0000000000000000000000000000000000000000..8f23276796037d048eb114952891a01a40971b3e
2022-06-09 23:11:43 +02:00
--- /dev/null
2023-10-04 03:06:23 +02:00
+++ b/src/main/java/io/papermc/paper/configuration/mapping/InnerClassFieldDiscoverer.java
@@ -0,0 +1,54 @@
+package io.papermc.paper.configuration.mapping;
2022-06-09 23:11:43 +02:00
+
2023-10-04 03:06:23 +02:00
+import io.papermc.paper.configuration.ConfigurationPart;
+import io.papermc.paper.configuration.Configurations;
+import io.papermc.paper.configuration.PaperConfigurations;
+import io.papermc.paper.configuration.WorldConfiguration;
+import java.lang.reflect.AnnotatedType;
+import java.lang.reflect.Field;
+import java.util.Collections;
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.spongepowered.configurate.objectmapping.FieldDiscoverer;
2022-06-09 23:11:43 +02:00
+import org.spongepowered.configurate.serialize.SerializationException;
+
2023-10-04 03:06:23 +02:00
+import static io.leangen.geantyref.GenericTypeReflector.erase;
2022-06-09 23:11:43 +02:00
+
2023-10-04 03:06:23 +02:00
+public final class InnerClassFieldDiscoverer implements FieldDiscoverer<Map<Field, Object>> {
2022-06-09 23:11:43 +02:00
+
2023-10-04 03:06:23 +02:00
+ private final InnerClassInstanceSupplier instanceSupplier;
+ private final FieldDiscoverer<Map<Field, Object>> delegate;
+
+ @SuppressWarnings("unchecked")
+ public InnerClassFieldDiscoverer(final Map<Class<?>, Object> initialOverrides) {
+ this.instanceSupplier = new InnerClassInstanceSupplier(initialOverrides);
+ this.delegate = (FieldDiscoverer<Map<Field, Object>>) FieldDiscoverer.object(this.instanceSupplier);
2022-06-09 23:11:43 +02:00
+ }
+
+ @Override
2023-10-04 03:06:23 +02:00
+ public @Nullable <V> InstanceFactory<Map<Field, Object>> discover(final AnnotatedType target, final 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 MutableInstanceFactory<Map<Field, Object>> mutableInstanceFactoryDelegate) {
+ return new InnerClassInstanceFactory(this.instanceSupplier, mutableInstanceFactoryDelegate, target);
+ }
+ }
+ return null;
2022-06-09 23:11:43 +02:00
+ }
+
2023-10-04 03:06:23 +02:00
+ public static FieldDiscoverer<?> worldConfig(WorldConfiguration worldConfiguration) {
+ final Map<Class<?>, Object> overrides = Map.of(
+ WorldConfiguration.class, worldConfiguration
+ );
+ return new InnerClassFieldDiscoverer(overrides);
+ }
+
+ public static FieldDiscoverer<?> globalConfig() {
+ return new InnerClassFieldDiscoverer(Collections.emptyMap());
2022-06-09 23:11:43 +02:00
+ }
+}
2023-10-04 03:06:23 +02:00
diff --git a/src/main/java/io/papermc/paper/configuration/mapping/InnerClassInstanceFactory.java b/src/main/java/io/papermc/paper/configuration/mapping/InnerClassInstanceFactory.java
2022-07-18 12:30:31 +02:00
new file mode 100644
2023-10-04 03:06:23 +02:00
index 0000000000000000000000000000000000000000..cec678ae24a7d99a46fa672be907f4c28fe4da96
2022-07-18 12:30:31 +02:00
--- /dev/null
2023-10-04 03:06:23 +02:00
+++ b/src/main/java/io/papermc/paper/configuration/mapping/InnerClassInstanceFactory.java
@@ -0,0 +1,65 @@
+package io.papermc.paper.configuration.mapping;
2022-07-18 12:30:31 +02:00
+
2023-10-04 03:06:23 +02:00
+import java.lang.reflect.AnnotatedType;
+import java.lang.reflect.Field;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.spongepowered.configurate.objectmapping.FieldDiscoverer;
2022-07-18 12:30:31 +02:00
+import org.spongepowered.configurate.serialize.SerializationException;
+
2023-10-04 03:06:23 +02:00
+import static io.leangen.geantyref.GenericTypeReflector.erase;
+
+final class InnerClassInstanceFactory implements FieldDiscoverer.MutableInstanceFactory<Map<Field, Object>> {
+
+ private final InnerClassInstanceSupplier instanceSupplier;
+ private final FieldDiscoverer.MutableInstanceFactory<Map<Field, Object>> fallback;
+ private final AnnotatedType targetType;
+
+ InnerClassInstanceFactory(final InnerClassInstanceSupplier instanceSupplier, final FieldDiscoverer.MutableInstanceFactory<Map<Field, Object>> fallback, final AnnotatedType targetType) {
+ this.instanceSupplier = instanceSupplier;
+ this.fallback = fallback;
+ this.targetType = targetType;
+ }
+
+ @Override
+ public Map<Field, Object> begin() {
+ return this.fallback.begin();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void complete(final Object instance, final 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(this.targetType.getType(), e);
+ }
+ this.fallback.complete(instance, intermediate);
+ }
+
+ @Override
+ public Object complete(final Map<Field, Object> intermediate) throws SerializationException {
+ final Object targetInstance = Objects.requireNonNull(this.instanceSupplier.instanceMap().get(erase(this.targetType.getType())), () -> this.targetType.getType() + " must already have an instance created");
+ this.complete(targetInstance, intermediate);
+ return targetInstance;
+ }
+
+ @Override
+ public boolean canCreateInstances() {
+ return this.fallback.canCreateInstances();
+ }
+}
diff --git a/src/main/java/io/papermc/paper/configuration/mapping/InnerClassInstanceSupplier.java b/src/main/java/io/papermc/paper/configuration/mapping/InnerClassInstanceSupplier.java
new file mode 100644
index 0000000000000000000000000000000000000000..8d8bc050441c02cf65dfcb6400978363d6b8ef10
--- /dev/null
+++ b/src/main/java/io/papermc/paper/configuration/mapping/InnerClassInstanceSupplier.java
@@ -0,0 +1,72 @@
+package io.papermc.paper.configuration.mapping;
+
+import io.papermc.paper.configuration.ConfigurationPart;
+import java.lang.reflect.AnnotatedType;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Supplier;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.spongepowered.configurate.serialize.SerializationException;
+import org.spongepowered.configurate.util.CheckedFunction;
+import org.spongepowered.configurate.util.CheckedSupplier;
+
+import static io.leangen.geantyref.GenericTypeReflector.erase;
+
+/**
+ * This instance factory handles creating non-static inner classes by tracking all instances of objects that extend
+ * {@link ConfigurationPart}. Only 1 instance of each {@link ConfigurationPart} should be present for each instance
+ * of the field discoverer this is used in.
+ */
+final class InnerClassInstanceSupplier implements CheckedFunction<AnnotatedType, @Nullable Supplier<Object>, SerializationException> {
+
+ private final Map<Class<?>, Object> instanceMap = new HashMap<>();
+ private final Map<Class<?>, Object> initialOverrides;
+
+ /**
+ * @param initialOverrides map of types to objects to preload the config objects with.
+ */
+ InnerClassInstanceSupplier(final Map<Class<?>, Object> initialOverrides) {
+ this.initialOverrides = initialOverrides;
+ }
+
+ @Override
+ public Supplier<Object> apply(final AnnotatedType target) throws SerializationException {
+ final Class<?> type = erase(target.getType());
+ if (this.initialOverrides.containsKey(type)) {
+ this.instanceMap.put(type, this.initialOverrides.get(type));
+ return () -> this.initialOverrides.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");
+ }
+ }
+
+ Map<Class<?>, Object> instanceMap() {
+ return this.instanceMap;
+ }
+
+}
diff --git a/src/main/java/io/papermc/paper/configuration/mapping/MergeMap.java b/src/main/java/io/papermc/paper/configuration/mapping/MergeMap.java
new file mode 100644
index 0000000000000000000000000000000000000000..471b161ac51900672434c6608595bb73c02d8180
--- /dev/null
+++ b/src/main/java/io/papermc/paper/configuration/mapping/MergeMap.java
@@ -0,0 +1,20 @@
+package io.papermc.paper.configuration.mapping;
+
+import io.papermc.paper.configuration.ConfigurationPart;
+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/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;
2022-07-18 12:30:31 +02:00
+
+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();
+ }
+}
2022-06-09 21:51:26 +02:00
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..8535c748a5e355362e77e6c5103e11c4c318a138
2022-06-09 21:51:26 +02:00
--- /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.getClassLogger();
2022-06-09 21:51:26 +02:00
+
+ 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/NbtPathSerializer.java b/src/main/java/io/papermc/paper/configuration/serializer/NbtPathSerializer.java
new file mode 100644
index 0000000000000000000000000000000000000000..b44b2dc28f619594e302417848e95c0087acbcea
--- /dev/null
+++ b/src/main/java/io/papermc/paper/configuration/serializer/NbtPathSerializer.java
@@ -0,0 +1,52 @@
+package io.papermc.paper.configuration.serializer;
+
+import com.destroystokyo.paper.util.SneakyThrow;
+import com.mojang.brigadier.StringReader;
+import com.mojang.brigadier.exceptions.CommandSyntaxException;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Predicate;
+import net.minecraft.commands.arguments.NbtPathArgument;
+import org.spongepowered.configurate.serialize.ScalarSerializer;
+import org.spongepowered.configurate.serialize.SerializationException;
+
+public class NbtPathSerializer extends ScalarSerializer<NbtPathArgument.NbtPath> {
+
+ public static final NbtPathSerializer SERIALIZER = new NbtPathSerializer();
+ private static final NbtPathArgument DUMMY_ARGUMENT = new NbtPathArgument();
+
+ private NbtPathSerializer() {
+ super(NbtPathArgument.NbtPath.class);
+ }
+
+ @Override
+ public NbtPathArgument.NbtPath deserialize(final Type type, final Object obj) throws SerializationException {
+ return fromString(obj.toString());
+ }
+
+ @Override
+ protected Object serialize(final NbtPathArgument.NbtPath item, final Predicate<Class<?>> typeSupported) {
+ return item.toString();
+ }
+
+ public static List<NbtPathArgument.NbtPath> fromString(final List<String> tags) {
+ List<NbtPathArgument.NbtPath> paths = new ArrayList<>();
+ try {
+ for (final String tag : tags) {
+ paths.add(fromString(tag));
+ }
+ } catch (SerializationException ex) {
+ SneakyThrow.sneaky(ex);
+ }
+ return List.copyOf(paths);
+ }
+
+ private static NbtPathArgument.NbtPath fromString(final String tag) throws SerializationException {
+ try {
+ return DUMMY_ARGUMENT.parse(new StringReader(tag));
+ } catch (CommandSyntaxException e) {
+ throw new SerializationException(NbtPathArgument.NbtPath.class, e);
+ }
+ }
+}
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..893ad5e7c2d32ccd64962d95d146bbd317c28ab8
--- /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.getClassLogger();
+ 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
2022-12-08 09:52:08 +01:00
index 0000000000000000000000000000000000000000..7fc0905fc6b8f5df762b4cea573f935dc00b8bc1
--- /dev/null
+++ b/src/main/java/io/papermc/paper/configuration/serializer/StringRepresentableSerializer.java
2022-12-08 09:52:08 +01:00
@@ -0,0 +1,52 @@
+package io.papermc.paper.configuration.serializer;
+
+import net.minecraft.util.StringRepresentable;
+import net.minecraft.world.entity.MobCategory;
2022-12-08 09:52:08 +01:00
+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.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(
2022-12-08 09:52:08 +01:00
+ createEntry(MobCategory.class)
+ ));
+
+ public StringRepresentableSerializer() {
+ super(StringRepresentable.class);
+ }
+
+ public static boolean isValidFor(final Type type) {
+ return TYPES.containsKey(type);
+ }
+
+ private static <E extends Enum<E> & StringRepresentable> Map.Entry<Type, Function<String, @Nullable StringRepresentable>> createEntry(Class<E> type) {
+ return Map.entry(type, s -> {
+ for (E value : type.getEnumConstants()) {
+ if (value.getSerializedName().equals(s)) {
+ return value;
+ }
+ }
+ return null;
2022-12-08 09:52:08 +01:00
+ });
+ }
+
+ @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/collections/FastutilMapSerializer.java b/src/main/java/io/papermc/paper/configuration/serializer/collections/FastutilMapSerializer.java
new file mode 100644
index 0000000000000000000000000000000000000000..4af710e144b70933d750c22edfe484c18e4a3540
--- /dev/null
+++ b/src/main/java/io/papermc/paper/configuration/serializer/collections/FastutilMapSerializer.java
@@ -0,0 +1,69 @@
+package io.papermc.paper.configuration.serializer.collections;
+
+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/collections/MapSerializer.java b/src/main/java/io/papermc/paper/configuration/serializer/collections/MapSerializer.java
new file mode 100644
index 0000000000000000000000000000000000000000..e7e997796ec47c742cc1f98e61db75a4231e6f98
--- /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.getClassLogger();
+
2022-06-09 21:51:26 +02:00
+ 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);
2022-06-09 21:51:26 +02:00
+ } catch (SerializationException ex) {
+ ex.initPath(node::path);
+ LOGGER.error("Could not deserialize {} {} into {} at {}: {}", mapPart, node.raw(), type, path, ex.rawMessage());
+ }
+ 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 {
2022-06-09 21:51:26 +02:00
+ 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());
2022-06-09 21:51:26 +02:00
+ 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, ex.rawMessage());
+ }
+ 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/collections/TableSerializer.java b/src/main/java/io/papermc/paper/configuration/serializer/collections/TableSerializer.java
new file mode 100644
index 0000000000000000000000000000000000000000..36ca88b677e1b55b41c52750948d5b6de7ecd007
--- /dev/null
+++ b/src/main/java/io/papermc/paper/configuration/serializer/collections/TableSerializer.java
@@ -0,0 +1,89 @@
+package io.papermc.paper.configuration.serializer.collections;
+
+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/registry/RegistryEntrySerializer.java b/src/main/java/io/papermc/paper/configuration/serializer/registry/RegistryEntrySerializer.java
new file mode 100644
2024-10-25 18:08:28 +02:00
index 0000000000000000000000000000000000000000..cb0de1a639578320fd38177a915bfa5d1e9a73bd
--- /dev/null
+++ b/src/main/java/io/papermc/paper/configuration/serializer/registry/RegistryEntrySerializer.java
@@ -0,0 +1,64 @@
+package io.papermc.paper.configuration.serializer.registry;
+
+import io.leangen.geantyref.TypeToken;
+import java.lang.reflect.Type;
+import java.util.function.Predicate;
+import net.minecraft.core.Registry;
+import net.minecraft.core.RegistryAccess;
+import net.minecraft.resources.ResourceKey;
+import net.minecraft.resources.ResourceLocation;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.spongepowered.configurate.serialize.ScalarSerializer;
+import org.spongepowered.configurate.serialize.SerializationException;
+
+abstract class RegistryEntrySerializer<T, R> extends ScalarSerializer<T> {
+
+ private final RegistryAccess registryAccess;
2022-06-09 21:51:26 +02:00
+ private final ResourceKey<? extends Registry<R>> registryKey;
+ private final boolean omitMinecraftNamespace;
+
+ protected RegistryEntrySerializer(TypeToken<T> type, final RegistryAccess registryAccess, ResourceKey<? extends Registry<R>> registryKey, boolean omitMinecraftNamespace) {
+ super(type);
+ this.registryAccess = registryAccess;
2022-06-09 21:51:26 +02:00
+ this.registryKey = registryKey;
+ this.omitMinecraftNamespace = omitMinecraftNamespace;
+ }
+
+ protected RegistryEntrySerializer(Class<T> type, final RegistryAccess registryAccess, ResourceKey<? extends Registry<R>> registryKey, boolean omitMinecraftNamespace) {
+ super(type);
+ this.registryAccess = registryAccess;
2022-06-09 21:51:26 +02:00
+ this.registryKey = registryKey;
+ this.omitMinecraftNamespace = omitMinecraftNamespace;
+ }
+
+ protected final Registry<R> registry() {
2024-10-25 18:08:28 +02:00
+ return this.registryAccess.lookupOrThrow(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);
+ }
2022-06-09 21:51:26 +02:00
+ 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
2024-10-25 18:08:28 +02:00
index 0000000000000000000000000000000000000000..76f6219eac049afef7ce03cd30d7c3232b5b9b7c
--- /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 java.util.function.Function;
+import net.minecraft.core.Holder;
+import net.minecraft.core.Registry;
+import net.minecraft.core.RegistryAccess;
+import net.minecraft.resources.ResourceKey;
+import org.spongepowered.configurate.serialize.SerializationException;
+
+public final class RegistryHolderSerializer<T> extends RegistryEntrySerializer<Holder<T>, T> {
+
+ @SuppressWarnings("unchecked")
+ public RegistryHolderSerializer(TypeToken<T> typeToken, final RegistryAccess registryAccess, ResourceKey<? extends Registry<T>> registryKey, boolean omitMinecraftNamespace) {
+ super((TypeToken<Holder<T>>) TypeToken.get(TypeFactory.parameterizedClass(Holder.class, typeToken.getType())), registryAccess, registryKey, omitMinecraftNamespace);
+ }
+
+ public RegistryHolderSerializer(Class<T> type, final RegistryAccess registryAccess, ResourceKey<? extends Registry<T>> registryKey, boolean omitMinecraftNamespace) {
+ this(TypeToken.get(type), registryAccess, 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 {
2024-10-25 18:08:28 +02:00
+ return this.registry().get(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
2024-10-25 18:08:28 +02:00
index 0000000000000000000000000000000000000000..6831b7b72c5e1f79eff36019ca2ff56531c26df8
--- /dev/null
+++ b/src/main/java/io/papermc/paper/configuration/serializer/registry/RegistryValueSerializer.java
@@ -0,0 +1,35 @@
+package io.papermc.paper.configuration.serializer.registry;
+
+import io.leangen.geantyref.TypeToken;
+import net.minecraft.core.Registry;
+import net.minecraft.core.RegistryAccess;
+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, final RegistryAccess registryAccess, ResourceKey<? extends Registry<T>> registryKey, boolean omitMinecraftNamespace) {
+ super(type, registryAccess, registryKey, omitMinecraftNamespace);
+ }
+
+ public RegistryValueSerializer(Class<T> type, final RegistryAccess registryAccess, ResourceKey<? extends Registry<T>> registryKey, boolean omitMinecraftNamespace) {
+ super(type, registryAccess, registryKey, omitMinecraftNamespace);
+ }
+
+ @Override
+ protected T convertFromResourceKey(ResourceKey<T> key) throws SerializationException {
2024-10-25 18:08:28 +02:00
+ final T value = this.registry().getValue(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..96e8d03bd4a4d43633a94bb251054610ac07315a
--- /dev/null
+++ b/src/main/java/io/papermc/paper/configuration/transformation/Transformations.java
@@ -0,0 +1,41 @@
+package io.papermc.paper.configuration.transformation;
+
+import io.papermc.paper.configuration.Configuration;
2022-06-09 21:51:26 +02:00
+import io.papermc.paper.configuration.Configurations;
+import org.spongepowered.configurate.ConfigurationNode;
+import org.spongepowered.configurate.NodePath;
+import org.spongepowered.configurate.transformation.ConfigurationTransformation;
+import org.spongepowered.configurate.transformation.TransformAction;
+
+import static org.spongepowered.configurate.NodePath.path;
+
+public 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;
+ });
+ }
2022-06-09 21:51:26 +02:00
+
+ public static ConfigurationTransformation.VersionedBuilder versionedBuilder() {
+ return ConfigurationTransformation.versionedBuilder().versionKey(Configuration.VERSION_FIELD);
+ }
+
2022-06-09 21:51:26 +02:00
+ @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..ef0e834c164b0ccc1a61b349348e6799733d66d9
--- /dev/null
+++ b/src/main/java/io/papermc/paper/configuration/transformation/global/LegacyPaperConfig.java
@@ -0,0 +1,223 @@
+package io.papermc.paper.configuration.transformation.global;
+
+import com.mojang.logging.LogUtils;
+import io.papermc.paper.configuration.Configuration;
2022-06-09 23:11:43 +02:00
+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;
2022-06-09 21:51:26 +02:00
+import net.minecraft.network.protocol.game.ServerboundPlaceRecipePacket;
2022-06-09 23:11:43 +02:00
+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;
+
2022-06-09 23:11:43 +02:00
+import java.util.function.Predicate;
+
+import static org.spongepowered.configurate.NodePath.path;
+
+public final class LegacyPaperConfig {
+ private static final Logger LOGGER = LogUtils.getClassLogger();
+
+ 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");
+
2022-06-09 23:11:43 +02:00
+ 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();
+ }
+
2022-06-09 23:11:43 +02:00
+ 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;
+ });
+ }
+
+ @SuppressWarnings("deprecation") // valid use to convert legacy string to mini-message in legacy migration
2022-06-09 23:11:43 +02:00
+ 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/global/versioned/V29_LogIPs.java b/src/main/java/io/papermc/paper/configuration/transformation/global/versioned/V29_LogIPs.java
new file mode 100644
index 0000000000000000000000000000000000000000..66073f7a6a96405348cc4044ad1e6922158b13ba
--- /dev/null
+++ b/src/main/java/io/papermc/paper/configuration/transformation/global/versioned/V29_LogIPs.java
@@ -0,0 +1,44 @@
+package io.papermc.paper.configuration.transformation.global.versioned;
+
+import java.util.Properties;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.dedicated.DedicatedServer;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.spongepowered.configurate.ConfigurateException;
+import org.spongepowered.configurate.ConfigurationNode;
+import org.spongepowered.configurate.NodePath;
+import org.spongepowered.configurate.transformation.ConfigurationTransformation;
+import org.spongepowered.configurate.transformation.TransformAction;
+
+import static org.spongepowered.configurate.NodePath.path;
+
+public class V29_LogIPs implements TransformAction {
+
+ private static final int VERSION = 29;
+ private static final NodePath PATH = path("logging", "log-player-ip-addresses");
+ private static final V29_LogIPs INSTANCE = new V29_LogIPs();
+
+ private V29_LogIPs() {
+ }
+
+ public static void apply(final ConfigurationTransformation.VersionedBuilder builder) {
+ builder.addVersion(VERSION, ConfigurationTransformation.builder().addAction(PATH, INSTANCE).build());
+ }
+
+ @Override
+ public Object @Nullable [] visitPath(final NodePath path, final ConfigurationNode value) throws ConfigurateException {
+ final DedicatedServer server = ((DedicatedServer) MinecraftServer.getServer());
+
+ final boolean val = value.getBoolean(server.settings.getProperties().logIPs);
+ server.settings.update((config) -> {
+ final Properties newProps = new Properties(config.properties);
+ newProps.setProperty("log-ips", String.valueOf(val));
+ return config.reload(server.registryAccess(), newProps, server.options);
+ });
+
+ value.raw(null);
+
+ return null;
+ }
+
+}
2022-06-09 21:51:26 +02:00
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
2024-10-25 18:08:28 +02:00
index 0000000000000000000000000000000000000000..cb1f5f65c098470dc8553b015d0f0f29f28ed956
2022-06-09 21:51:26 +02:00
--- /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;
2022-12-08 04:24:00 +01:00
+import net.minecraft.core.registries.Registries;
2022-06-09 21:51:26 +02:00
+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 final class FeatureSeedsGeneration implements TransformAction {
2022-06-09 21:51:26 +02:00
+
+ 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.getClassLogger();
2022-06-09 21:51:26 +02:00
+
+ 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);
2024-10-25 18:08:28 +02:00
+ MinecraftServer.getServer().registryAccess().lookupOrThrow(Registries.CONFIGURED_FEATURE).listElements().forEach(holder -> {
2022-06-09 21:51:26 +02:00
+ 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 (defaultsNode.node(FEATURE_SEEDS_KEY, GENERATE_KEY).getBoolean(false)) {
2022-06-09 21:51:26 +02:00
+ 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
2024-10-25 18:08:28 +02:00
index 0000000000000000000000000000000000000000..77e530830dc8ebc861b2e70f787f9b71524a54d2
--- /dev/null
+++ b/src/main/java/io/papermc/paper/configuration/transformation/world/LegacyPaperWorldConfig.java
2022-12-08 04:24:00 +01:00
@@ -0,0 +1,322 @@
+package io.papermc.paper.configuration.transformation.world;
+
+import io.papermc.paper.configuration.Configuration;
+import io.papermc.paper.configuration.WorldConfiguration;
+import net.minecraft.core.Holder;
2022-12-08 04:24:00 +01:00
+import net.minecraft.core.registries.BuiltInRegistries;
+import net.minecraft.core.registries.Registries;
+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;
+
2022-06-09 21:51:26 +02:00
+import java.util.HashMap;
+import java.util.List;
2022-06-10 00:03:02 +02:00
+import java.util.Locale;
2022-06-09 21:51:26 +02:00
+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)
2022-06-10 00:03:02 +02:00
+ .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);
2022-06-10 00:03:02 +02:00
+ } 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);
2022-06-10 00:03:02 +02:00
+ } 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();
2024-10-25 18:08:28 +02:00
+ final Optional<Holder.Reference<Item>> item = BuiltInRegistries.ITEM.get(ResourceKey.create(Registries.ITEM, ResourceLocation.parse(itemName.toLowerCase(Locale.ROOT))));
+ if (item.isEmpty()) {
2022-06-10 00:03:02 +02:00
+ 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;
2022-06-09 21:51:26 +02:00
+ 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();
+ })
2022-06-10 03:40:07 +02:00
+ .addAction(path("alt-item-despawn-rate", "items"), (path, value) -> {
2022-06-09 21:51:26 +02:00
+ if (value.isMap()) {
+ Map<String, Integer> rebuild = new HashMap<>();
+ value.childrenMap().forEach((key, node) -> {
+ String itemName = key.toString();
2024-10-25 18:08:28 +02:00
+ final Optional<Holder.Reference<Item>> itemHolder = BuiltInRegistries.ITEM.get(ResourceKey.create(Registries.ITEM, ResourceLocation.parse(itemName.toLowerCase(Locale.ROOT))));
2022-06-09 21:51:26 +02:00
+ 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() {
2022-06-10 00:15:01 +02:00
+ 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");
2022-06-10 07:03:37 +02:00
+ 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");
2021-06-11 14:02:28 +02:00
+
+ 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");
2022-06-09 21:51:26 +02:00
+ moveFromRoot(builder, "fix-wither-targeting-bug", "fixes");
+ moveFromGameMechanics(builder, "disable-unloaded-chunk-enderpearl-exploit", "fixes");
+ moveFromGameMechanics(builder, "fix-curing-zombie-villager-discount-exploit", "fixes");
2021-06-11 14:02:28 +02:00
+
+ builder.addAction(path("fishing-time-range", "MaximumTicks"), TransformAction.rename("maximum"));
+ builder.addAction(path("fishing-time-range", "MinimumTicks"), TransformAction.rename("minimum"));
2021-06-11 14:02:28 +02:00
+
+ 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();
2021-06-11 14:02:28 +02:00
+ }
+
+ private static void moveToMisc(final ConfigurationTransformation.Builder builder, String... key) {
+ moveFromRootAndRename(builder, path((Object[]) key), key[key.length - 1], "misc");
2021-06-11 14:02:28 +02:00
+ }
+
+ private static void moveFromGameMechanics(final ConfigurationTransformation.Builder builder, final String key, final String... parents) {
+ moveFromGameMechanicsAndRename(builder, key, key, parents);
2021-06-11 14:02:28 +02:00
+ }
+
+ 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);
2021-06-11 14:02:28 +02:00
+ }
+}
diff --git a/src/main/java/io/papermc/paper/configuration/transformation/world/versioned/V29_ZeroWorldHeight.java b/src/main/java/io/papermc/paper/configuration/transformation/world/versioned/V29_ZeroWorldHeight.java
new file mode 100644
2023-10-04 03:06:23 +02:00
index 0000000000000000000000000000000000000000..6e481d509d091e65a4909d79014ac94ea63c8455
--- /dev/null
+++ b/src/main/java/io/papermc/paper/configuration/transformation/world/versioned/V29_ZeroWorldHeight.java
@@ -0,0 +1,49 @@
+package io.papermc.paper.configuration.transformation.world.versioned;
+
2023-10-04 03:06:23 +02:00
+import io.papermc.paper.configuration.type.number.IntOr;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.spongepowered.configurate.ConfigurateException;
+import org.spongepowered.configurate.ConfigurationNode;
+import org.spongepowered.configurate.NodePath;
+import org.spongepowered.configurate.transformation.ConfigurationTransformation;
+import org.spongepowered.configurate.transformation.TransformAction;
+
+import static org.spongepowered.configurate.NodePath.path;
+
+/**
+ * Several configurations that set a y-level used '0' as the "disabled" value.
+ * Since 0 is now a valid value, they need to be updated.
+ */
+public final class V29_ZeroWorldHeight implements TransformAction {
+
+ private static final int VERSION = 29;
+
+ private static final String FIXES_KEY = "fixes";
+ private static final String FALLING_BLOCK_HEIGHT_NERF_KEY = "falling-block-height-nerf";
+ private static final String TNT_ENTITY_HEIGHT_NERF_KEY = "tnt-entity-height-nerf";
+
+ private static final String ENVIRONMENT_KEY = "environment";
+ private static final String NETHER_CEILING_VOID_DAMAGE_HEIGHT_KEY = "nether-ceiling-void-damage-height";
+
+ private static final V29_ZeroWorldHeight INSTANCE = new V29_ZeroWorldHeight();
+
+ private V29_ZeroWorldHeight() {
+ }
+
+ public static void apply(ConfigurationTransformation.VersionedBuilder builder) {
+ final ConfigurationTransformation transformation = ConfigurationTransformation.builder()
+ .addAction(path(FIXES_KEY, FALLING_BLOCK_HEIGHT_NERF_KEY), INSTANCE)
+ .addAction(path(FIXES_KEY, TNT_ENTITY_HEIGHT_NERF_KEY), INSTANCE)
+ .addAction(path(ENVIRONMENT_KEY, NETHER_CEILING_VOID_DAMAGE_HEIGHT_KEY), INSTANCE)
+ .build();
+ builder.addVersion(VERSION, transformation);
+ }
+
+ @Override
+ public Object @Nullable [] visitPath(NodePath path, ConfigurationNode value) throws ConfigurateException {
+ if (value.getInt() == 0) {
+ value.set(IntOr.Disabled.DISABLED);
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/io/papermc/paper/configuration/transformation/world/versioned/V30_RenameFilterNbtFromSpawnEgg.java b/src/main/java/io/papermc/paper/configuration/transformation/world/versioned/V30_RenameFilterNbtFromSpawnEgg.java
new file mode 100644
index 0000000000000000000000000000000000000000..d08b65234192d5b639cead675114f64bf1f409c4
--- /dev/null
+++ b/src/main/java/io/papermc/paper/configuration/transformation/world/versioned/V30_RenameFilterNbtFromSpawnEgg.java
@@ -0,0 +1,25 @@
+package io.papermc.paper.configuration.transformation.world.versioned;
+
+import org.spongepowered.configurate.NodePath;
+import org.spongepowered.configurate.transformation.ConfigurationTransformation;
+
+import static org.spongepowered.configurate.NodePath.path;
+import static org.spongepowered.configurate.transformation.TransformAction.rename;
+
+/**
+ * The {@code filter-nbt-data-from-spawn-eggs-and-related} setting had nothing
+ * to do with spawn eggs, and was just filtering bad falling blocks.
+ */
+public final class V30_RenameFilterNbtFromSpawnEgg {
+
+ private static final int VERSION = 30;
+ private static final NodePath OLD_PATH = path("entities", "spawning", "filter-nbt-data-from-spawn-eggs-and-related");
+ private static final String NEW_PATH = "filter-bad-tile-entity-nbt-from-falling-blocks";
+
+ private V30_RenameFilterNbtFromSpawnEgg() {
+ }
+
+ public static void apply(ConfigurationTransformation.VersionedBuilder builder) {
+ builder.addVersion(VERSION, ConfigurationTransformation.builder().addAction(OLD_PATH, rename(NEW_PATH)).build());
+ }
+}
diff --git a/src/main/java/io/papermc/paper/configuration/transformation/world/versioned/V31_SpawnLoadedRangeToGameRule.java b/src/main/java/io/papermc/paper/configuration/transformation/world/versioned/V31_SpawnLoadedRangeToGameRule.java
new file mode 100644
index 0000000000000000000000000000000000000000..d872b1948df52759fed9c3d892aed6abfdfc8068
--- /dev/null
+++ b/src/main/java/io/papermc/paper/configuration/transformation/world/versioned/V31_SpawnLoadedRangeToGameRule.java
@@ -0,0 +1,55 @@
+package io.papermc.paper.configuration.transformation.world.versioned;
+
+import io.papermc.paper.configuration.Configurations;
+import net.minecraft.world.level.GameRules;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.spongepowered.configurate.ConfigurationNode;
+import org.spongepowered.configurate.NodePath;
+import org.spongepowered.configurate.transformation.ConfigurationTransformation;
+import org.spongepowered.configurate.transformation.TransformAction;
+
+import static org.spongepowered.configurate.NodePath.path;
+
+public final class V31_SpawnLoadedRangeToGameRule implements TransformAction {
+
+ private static final int VERSION = 31;
+ private static final String SPAWN = "spawn";
+ private static final String KEEP_SPAWN_LOADED_RANGE = "keep-spawn-loaded-range";
+ private static final String KEEP_SPAWN_LOADED = "keep-spawn-loaded";
+
+ private final GameRules gameRules;
+ private final ConfigurationNode defaultsNode;
+
+ private V31_SpawnLoadedRangeToGameRule(final GameRules gameRules, final ConfigurationNode defaultsNode) {
+ this.gameRules = gameRules;
+ this.defaultsNode = defaultsNode;
+ }
+
+ @Override
+ public Object @Nullable [] visitPath(final NodePath path, final ConfigurationNode value) {
+ final ConfigurationNode worldSpawnNode = value.node(SPAWN);
+ final ConfigurationNode worldLoadedNode = worldSpawnNode.node(KEEP_SPAWN_LOADED);
+ final boolean keepLoaded = worldLoadedNode.getBoolean(this.defaultsNode.node(SPAWN, KEEP_SPAWN_LOADED).getBoolean());
+ worldLoadedNode.raw(null);
+ final ConfigurationNode worldRangeNode = worldSpawnNode.node(KEEP_SPAWN_LOADED_RANGE);
+ final int range = worldRangeNode.getInt(this.defaultsNode.node(SPAWN, KEEP_SPAWN_LOADED_RANGE).getInt());
+ worldRangeNode.raw(null);
+ if (worldSpawnNode.empty()) {
+ worldSpawnNode.raw(null);
+ }
+ if (!keepLoaded) {
+ this.gameRules.getRule(GameRules.RULE_SPAWN_CHUNK_RADIUS).set(0, null);
+ } else {
+ this.gameRules.getRule(GameRules.RULE_SPAWN_CHUNK_RADIUS).set(range, null);
+ }
+ return null;
+ }
+
+ public static void apply(final ConfigurationTransformation.VersionedBuilder builder, final Configurations.ContextMap contextMap, final @Nullable ConfigurationNode defaultsNode) {
+ if (defaultsNode != null) {
+ builder.addVersion(VERSION, ConfigurationTransformation.builder().addAction(path(), new V31_SpawnLoadedRangeToGameRule(contextMap.require(Configurations.GAME_RULES), defaultsNode)).build());
+ } else {
+ builder.addVersion(VERSION, ConfigurationTransformation.empty()); // increment version of default world config
+ }
+ }
+}
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
Update upstream (Bukkit/CraftBukkit/Spigot) (#10875) Upstream has released updates that appear to apply and compile correctly. This update has not been tested by PaperMC and as with ANY update, please do your own testing Bukkit Changes: 376e37db SPIGOT-7677: Update which entities are marked as spawnable 06c4add3 SPIGOT-7737: Add separate TreeType.MEGA_PINE 19b7caaa SPIGOT-7731: Spawn eggs cannot have damage e585297e PR-1022: Add force option to Player#spawnParticle d26e0094 PR-1018: Add methods to get players seeing specific chunks 8df1ed18 PR-978: Add Material#isCompostable and Material#getCompostChance 4b9b59c7 SPIGOT-7676: Enforce locale parameter in toLowerCase and toUpperCase method calls and always use root locale 8d1e700a PR-1020: Cast instead of using #typed when getting BlockType and ItemType to better work with testing / mocks fa28607a PR-1016: Fix incorrect assumption of Fireball having constant speed 4c6c8586 PR-1015: Add a tool component to ItemMeta 6f6b2123 PR-1014: Add PotionEffectTypeCategory to distinguish between beneficial and harmful effects f511cfe1 PR-1013, SPIGOT-4288, SPIGOT-6202: Add material rerouting in preparation for the switch to ItemType and BlockType def44cbf SPIGOT-7669: Fix typo in ProjectileHitEvent#getHitBlockFace documentation 53fa4f72 PR-1011: Throw an exception if a RecipeChoice is ever supplied air CraftBukkit Changes: ee95e171a SPIGOT-7737: Add separate TreeType.MEGA_PINE 0dae4c62c Fix spawn egg equality check and copy constructor ab59e847c Fix spawn eggs with no entity creating invalid stacks and disconnect creative clients 3b6093b28 SPIGOT-7736: Creative spawn egg use loses components c6b4d5a87 SPIGOT-7731: Spawn eggs cannot have damage 340ccd57f SPIGOT-7735: Fix serialization of player heads with note block sound fd2f41834 SPIGOT-7734: Can't register a custom advancement using unsafe() 02456e2a5 PR-1413: Add force option to Player#spawnParticle 6a61f38b2 SPIGOT-7680: Per-world weather command 58c41cebb PR-1409: Add methods to get players seeing specific chunks 16c976797 PR-1412: Fix shipwreck loot tables not being set for BlockTransformers 7189ba636 PR-1360: Add Material#isCompostable and Material#getCompostChance 900384556 SPIGOT-7676: Enforce locale parameter in toLowerCase and toUpperCase method calls and always use root locale bdb40c5f1 Increase outdated build delay d6607c7dd SPIGOT-7675: Fix FoodComponent config deserialization b148ed332 PR-1406: Fix incorrect assumption of Fireball having constant speed 3ec31ca75 PR-1405: Add a tool component to ItemMeta 5d7d675b9 PR-1404: Add PotionEffectTypeCategory to distinguish between beneficial and harmful effects 960827981 PR-1403, SPIGOT-4288, SPIGOT-6202: Add material rerouting in preparation for the switch to ItemType and BlockType 94e44ec93 PR-1401: Add a config option to accept old keys in registry get calls a43701920 PR-1402: Fix ChunkSnapshot#isSectionEmpty() is always false 87d0a3368 SPIGOT-7668: Move NONE Registry updater to FieldRename to avoid some class loader issues 2ea1e7ac2 PR-1399: Fix regression preventing positive .setDamage value from causing knockback for 0 damage events ba2d49d21 Increase outdated build delay Spigot Changes: fcd94e21 Rebuild patches 342f4939 SPIGOT-7661: Add experimental unload-frozen-chunks option
2024-06-13 16:45:27 +02:00
index 0000000000000000000000000000000000000000..a3eaa47cfcfc4fd2a607f9b375230fada35620d3
--- /dev/null
+++ b/src/main/java/io/papermc/paper/configuration/type/BooleanOrDefault.java
@@ -0,0 +1,53 @@
+package io.papermc.paper.configuration.type;
2021-06-11 14:02:28 +02:00
+
+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 ? fallback : this.value;
2021-06-11 14:02:28 +02:00
+ }
+
+ 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 {
Update upstream (Bukkit/CraftBukkit/Spigot) (#10875) Upstream has released updates that appear to apply and compile correctly. This update has not been tested by PaperMC and as with ANY update, please do your own testing Bukkit Changes: 376e37db SPIGOT-7677: Update which entities are marked as spawnable 06c4add3 SPIGOT-7737: Add separate TreeType.MEGA_PINE 19b7caaa SPIGOT-7731: Spawn eggs cannot have damage e585297e PR-1022: Add force option to Player#spawnParticle d26e0094 PR-1018: Add methods to get players seeing specific chunks 8df1ed18 PR-978: Add Material#isCompostable and Material#getCompostChance 4b9b59c7 SPIGOT-7676: Enforce locale parameter in toLowerCase and toUpperCase method calls and always use root locale 8d1e700a PR-1020: Cast instead of using #typed when getting BlockType and ItemType to better work with testing / mocks fa28607a PR-1016: Fix incorrect assumption of Fireball having constant speed 4c6c8586 PR-1015: Add a tool component to ItemMeta 6f6b2123 PR-1014: Add PotionEffectTypeCategory to distinguish between beneficial and harmful effects f511cfe1 PR-1013, SPIGOT-4288, SPIGOT-6202: Add material rerouting in preparation for the switch to ItemType and BlockType def44cbf SPIGOT-7669: Fix typo in ProjectileHitEvent#getHitBlockFace documentation 53fa4f72 PR-1011: Throw an exception if a RecipeChoice is ever supplied air CraftBukkit Changes: ee95e171a SPIGOT-7737: Add separate TreeType.MEGA_PINE 0dae4c62c Fix spawn egg equality check and copy constructor ab59e847c Fix spawn eggs with no entity creating invalid stacks and disconnect creative clients 3b6093b28 SPIGOT-7736: Creative spawn egg use loses components c6b4d5a87 SPIGOT-7731: Spawn eggs cannot have damage 340ccd57f SPIGOT-7735: Fix serialization of player heads with note block sound fd2f41834 SPIGOT-7734: Can't register a custom advancement using unsafe() 02456e2a5 PR-1413: Add force option to Player#spawnParticle 6a61f38b2 SPIGOT-7680: Per-world weather command 58c41cebb PR-1409: Add methods to get players seeing specific chunks 16c976797 PR-1412: Fix shipwreck loot tables not being set for BlockTransformers 7189ba636 PR-1360: Add Material#isCompostable and Material#getCompostChance 900384556 SPIGOT-7676: Enforce locale parameter in toLowerCase and toUpperCase method calls and always use root locale bdb40c5f1 Increase outdated build delay d6607c7dd SPIGOT-7675: Fix FoodComponent config deserialization b148ed332 PR-1406: Fix incorrect assumption of Fireball having constant speed 3ec31ca75 PR-1405: Add a tool component to ItemMeta 5d7d675b9 PR-1404: Add PotionEffectTypeCategory to distinguish between beneficial and harmful effects 960827981 PR-1403, SPIGOT-4288, SPIGOT-6202: Add material rerouting in preparation for the switch to ItemType and BlockType 94e44ec93 PR-1401: Add a config option to accept old keys in registry get calls a43701920 PR-1402: Fix ChunkSnapshot#isSectionEmpty() is always false 87d0a3368 SPIGOT-7668: Move NONE Registry updater to FieldRename to avoid some class loader issues 2ea1e7ac2 PR-1399: Fix regression preventing positive .setDamage value from causing knockback for 0 damage events ba2d49d21 Increase outdated build delay Spigot Changes: fcd94e21 Rebuild patches 342f4939 SPIGOT-7661: Add experimental unload-frozen-chunks option
2024-06-13 16:45:27 +02:00
+ return new BooleanOrDefault(BooleanUtils.toBoolean(string.toLowerCase(Locale.ROOT), "true", "false"));
+ } catch (IllegalArgumentException ex) {
+ throw new SerializationException(BooleanOrDefault.class, obj + "(" + type + ") is not a boolean or '" + DEFAULT_VALUE + "'", ex);
2021-06-11 14:02:28 +02:00
+ }
+ } else if (obj instanceof Boolean bool) {
+ return new BooleanOrDefault(bool);
2021-06-11 14:02:28 +02:00
+ }
2023-10-04 03:06:23 +02:00
+ throw new SerializationException(BooleanOrDefault.class, obj + "(" + type + ") is not a boolean or '" + DEFAULT_VALUE + "'");
2021-06-11 14:02:28 +02:00
+ }
+
+ @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/DespawnRange.java b/src/main/java/io/papermc/paper/configuration/type/DespawnRange.java
new file mode 100644
index 0000000000000000000000000000000000000000..8a792a000e924b3ddc572edc788598811e9ef71c
--- /dev/null
+++ b/src/main/java/io/papermc/paper/configuration/type/DespawnRange.java
@@ -0,0 +1,109 @@
+package io.papermc.paper.configuration.type;
+
+import io.papermc.paper.configuration.type.number.IntOr;
+import java.lang.reflect.Type;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.spongepowered.configurate.ConfigurationNode;
+import org.spongepowered.configurate.serialize.SerializationException;
+import org.spongepowered.configurate.serialize.TypeSerializer;
+
+/*
+(x/a)^2 + (y/b)^2 + (z/c)^2 < 1
+a == c
+ac = horizontal limit
+b = vertical limit
+x^2/ac^2 + y^2/b^2 + z^2/ac^2 < 1
+(x^2 + z^2)/ac^2 + y^2/b^2 < 1
+x^2 + z^2 + (y^2 * (ac^2/b^2)) < ac^2
+ */
+public final class DespawnRange {
+
+ public static final TypeSerializer<DespawnRange> SERIALIZER = new Serializer();
+
+ private final IntOr.Default horizontalLimit;
+ private final IntOr.Default verticalLimit;
+ private final boolean wasDefinedViaLongSyntax;
+
+ // cached values
+ private double preComputedHorizontalLimitSquared; // ac^2
+ private double preComputedHorizontalLimitSquaredOverVerticalLimitSquared; // ac^2/b^2
+ private int preComputedVanillaDefaultLimit;
+
+ public DespawnRange(final IntOr.Default generalLimit) {
+ this(generalLimit, generalLimit, false);
+ }
+
+ public DespawnRange(final IntOr.Default horizontalLimit, final IntOr.Default verticalLimit, final boolean wasDefinedViaLongSyntax) {
+ this.horizontalLimit = horizontalLimit;
+ this.verticalLimit = verticalLimit;
+ this.wasDefinedViaLongSyntax = wasDefinedViaLongSyntax;
+ }
+
+ public void preComputed(int defaultDistanceLimit, String identifier) throws SerializationException {
+ if (this.verticalLimit.or(defaultDistanceLimit) <= 0) {
+ throw new SerializationException("Vertical limit must be greater than 0 for " + identifier);
+ }
+ if (this.horizontalLimit.or(defaultDistanceLimit) <= 0) {
+ throw new SerializationException("Horizontal limit must be greater than 0 for " + identifier);
+ }
+ this.preComputedVanillaDefaultLimit = defaultDistanceLimit;
+ this.preComputedHorizontalLimitSquared = Math.pow(this.horizontalLimit.or(defaultDistanceLimit), 2);
+ if (!this.horizontalLimit.isDefined() && !this.verticalLimit.isDefined()) {
+ this.preComputedHorizontalLimitSquaredOverVerticalLimitSquared = 1.0;
+ } else {
+ this.preComputedHorizontalLimitSquaredOverVerticalLimitSquared = this.preComputedHorizontalLimitSquared / Math.pow(this.verticalLimit.or(defaultDistanceLimit), 2);
+ }
+ }
+
+ public boolean shouldDespawn(final Shape shape, final double dxSqr, final double dySqr, final double dzSqr, final double dy) {
+ if (shape == Shape.ELLIPSOID) {
+ return dxSqr + dzSqr + (dySqr * this.preComputedHorizontalLimitSquaredOverVerticalLimitSquared) > this.preComputedHorizontalLimitSquared;
+ } else {
+ return dxSqr + dzSqr > this.preComputedHorizontalLimitSquared || dy > this.verticalLimit.or(this.preComputedVanillaDefaultLimit);
+ }
+ }
+
+ public boolean wasDefinedViaLongSyntax() {
+ return this.wasDefinedViaLongSyntax;
+ }
+
+ public enum Shape {
+ CYLINDER, ELLIPSOID
+ }
+
+ static final class Serializer implements TypeSerializer<DespawnRange> {
+
+ public static final String HORIZONTAL = "horizontal";
+ public static final String VERTICAL = "vertical";
+
+ @Override
+ public DespawnRange deserialize(final Type type, final ConfigurationNode node) throws SerializationException {
+ if (node.hasChild(HORIZONTAL) && node.hasChild(VERTICAL)) {
+ return new DespawnRange(
+ node.node(HORIZONTAL).require(IntOr.Default.class),
+ node.node(VERTICAL).require(IntOr.Default.class),
+ true
+ );
+ } else if (node.hasChild(HORIZONTAL) || node.hasChild(VERTICAL)) {
+ throw new SerializationException(node, DespawnRange.class, "Expected both horizontal and vertical despawn ranges to be defined");
+ } else {
+ return new DespawnRange(node.require(IntOr.Default.class));
+ }
+ }
+
+ @Override
+ public void serialize(final Type type, final @Nullable DespawnRange despawnRange, final ConfigurationNode node) throws SerializationException {
+ if (despawnRange == null) {
+ node.raw(null);
+ return;
+ }
+
+ if (despawnRange.wasDefinedViaLongSyntax()) {
+ node.node(HORIZONTAL).set(despawnRange.horizontalLimit);
+ node.node(VERTICAL).set(despawnRange.verticalLimit);
+ } else {
+ node.set(despawnRange.verticalLimit);
+ }
+ }
+ }
+}
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..422ccb0b332b3e94be228b9b94f379467d6461a5
--- /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 {
2021-06-11 14:02:28 +02:00
+
+ 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) {
2021-06-11 14:02:28 +02:00
+ 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;
+ }
+
+ static final class Serializer extends ScalarSerializer<Duration> {
+ private Serializer() {
+ super(Duration.class);
2021-06-11 14:02:28 +02:00
+ }
+
+ @Override
+ public Duration deserialize(Type type, Object obj) throws SerializationException {
+ return new Duration(obj.toString());
2021-06-11 14:02:28 +02:00
+ }
+
+ @Override
+ protected Object serialize(Duration item, Predicate<Class<?>> typeSupported) {
+ return item.value();
2021-06-11 14:02:28 +02:00
+ }
+ }
+}
diff --git a/src/main/java/io/papermc/paper/configuration/type/DurationOrDisabled.java b/src/main/java/io/papermc/paper/configuration/type/DurationOrDisabled.java
new file mode 100644
index 0000000000000000000000000000000000000000..3f17e75e08e1cb4359b96a78c5b8d5284c484e43
--- /dev/null
+++ b/src/main/java/io/papermc/paper/configuration/type/DurationOrDisabled.java
@@ -0,0 +1,54 @@
+package io.papermc.paper.configuration.type;
+
+import java.lang.reflect.Type;
+import java.util.Optional;
+import java.util.function.Predicate;
+import org.spongepowered.configurate.serialize.ScalarSerializer;
+import org.spongepowered.configurate.serialize.SerializationException;
+
+@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
+public final class DurationOrDisabled {
+ private static final String DISABLE_VALUE = "disabled";
+ public static final DurationOrDisabled USE_DISABLED = new DurationOrDisabled(Optional.empty());
+ public static final ScalarSerializer<DurationOrDisabled> SERIALIZER = new Serializer();
+
+ private Optional<Duration> value;
+
+ public DurationOrDisabled(final Optional<Duration> value) {
+ this.value = value;
+ }
+
+ public Optional<Duration> value() {
+ return this.value;
+ }
+
+ public void value(final Optional<Duration> value) {
+ this.value = value;
+ }
+
+ public Duration or(final Duration fallback) {
+ return this.value.orElse(fallback);
+ }
+
+ private static final class Serializer extends ScalarSerializer<DurationOrDisabled> {
+ Serializer() {
+ super(DurationOrDisabled.class);
+ }
+
+ @Override
+ public DurationOrDisabled deserialize(final Type type, final Object obj) throws SerializationException {
+ if (obj instanceof final String string) {
+ if (DISABLE_VALUE.equalsIgnoreCase(string)) {
+ return USE_DISABLED;
+ }
+ return new DurationOrDisabled(Optional.of(Duration.SERIALIZER.deserialize(string)));
+ }
+ throw new SerializationException(obj + "(" + type + ") is not a duration or '" + DISABLE_VALUE + "'");
+ }
+
+ @Override
+ protected Object serialize(final DurationOrDisabled item, final Predicate<Class<?>> typeSupported) {
+ return item.value.map(Duration::value).orElse(DISABLE_VALUE);
+ }
+ }
+}
2022-07-18 12:30:31 +02:00
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..7f8b685762f59049fde88e8d1bc10e1504916010
2022-07-18 12:30:31 +02:00
--- /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"), OBFUSCATE_LAYER(3, "obfuscate layer");
2022-07-18 12:30:31 +02:00
+
+ 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/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;
2021-06-11 14:02:28 +02:00
+
+public class ArrowDespawnRate extends FallbackValue.Int {
+
+ ArrowDespawnRate(Map<ContextKey<?>, Object> context, Object value) throws SerializationException {
+ super(context, fromObject(value));
2021-06-11 14:02:28 +02:00
+ }
+
+ private ArrowDespawnRate(Map<ContextKey<?>, Object> context) {
+ super(context, OptionalInt.empty());
2021-06-11 14:02:28 +02:00
+ }
+
+ @Override
+ protected OptionalInt process(int value) {
+ return Util.negToDef(value);
2021-06-11 14:02:28 +02:00
+ }
+
+ @Override
+ public Set<ContextKey<?>> required() {
+ return Set.of(FallbackValue.SPIGOT_WORLD_CONFIG);
2021-06-11 14:02:28 +02:00
+ }
+
+ @Override
+ protected int fallback() {
+ return this.get(FallbackValue.SPIGOT_WORLD_CONFIG).arrowDespawnRate;
2021-06-11 14:02:28 +02:00
+ }
+
+ public static ArrowDespawnRate def(SpigotWorldConfig spigotConfig) {
+ return new ArrowDespawnRate(FallbackValue.SPIGOT_WORLD_CONFIG.singleton(spigotConfig));
2021-06-11 14:02:28 +02:00
+ }
+}
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
2021-06-11 14:02:28 +02:00
new file mode 100644
index 0000000000000000000000000000000000000000..0f2765b2edc63c11ba3c57ff55c536054826a995
2021-06-11 14:02:28 +02:00
--- /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;
2021-06-11 14:02:28 +02:00
+
+import net.minecraft.server.MinecraftServer;
+import org.spongepowered.configurate.serialize.SerializationException;
2021-06-11 14:02:28 +02:00
+
+import java.util.Map;
+import java.util.OptionalInt;
+import java.util.Set;
+import java.util.function.Supplier;
2021-06-11 14:02:28 +02:00
+
+public class AutosavePeriod extends FallbackValue.Int {
2021-06-11 14:02:28 +02:00
+
+ AutosavePeriod(Map<ContextKey<?>, Object> contextMap, Object value) throws SerializationException {
+ super(contextMap, fromObject(value));
+ }
2021-06-11 14:02:28 +02:00
+
+ private AutosavePeriod(Map<ContextKey<?>, Object> contextMap) {
+ super(contextMap, OptionalInt.empty());
+ }
2021-06-11 14:02:28 +02:00
+
+ @Override
+ protected OptionalInt process(int value) {
+ return Util.negToDef(value);
2021-06-11 14:02:28 +02:00
+ }
+
+ @Override
+ protected Set<ContextKey<?>> required() {
+ return Set.of(FallbackValue.MINECRAFT_SERVER);
2021-06-11 14:02:28 +02:00
+ }
+
+ @Override
+ protected int fallback() {
+ return this.get(FallbackValue.MINECRAFT_SERVER).get().autosavePeriod;
2021-06-11 14:02:28 +02:00
+ }
+
+ 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
2022-06-17 20:28:18 +02:00
index 0000000000000000000000000000000000000000..a3a1d398d783c37914fb6d646e11361afee687b8
--- /dev/null
+++ b/src/main/java/io/papermc/paper/configuration/type/fallback/FallbackValue.java
2022-06-17 20:28:18 +02:00
@@ -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;
+
2022-06-17 20:28:18 +02:00
+@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 + "'");
+ }
2021-06-11 14:02:28 +02:00
+ }
+
+ 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);
+ }
2021-06-11 14:02:28 +02:00
+ }
+}
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;
2021-06-11 14:02:28 +02:00
+
+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;
2021-06-11 14:02:28 +02:00
+ }
+
+ @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);
2021-06-11 14:02:28 +02:00
+ }
+
+ @Override
+ protected Object serialize(FallbackValue item, Predicate<Class<?>> typeSupported) {
+ return item.serialize();
2021-06-11 14:02:28 +02:00
+ }
+
+ public static FallbackValueSerializer create(SpigotWorldConfig config, Supplier<MinecraftServer> server) {
+ return new FallbackValueSerializer(Map.of(FallbackValue.SPIGOT_WORLD_CONFIG, config, FallbackValue.MINECRAFT_SERVER, server));
2021-06-11 14:02:28 +02:00
+ }
+}
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;
2021-11-24 18:58:26 +01:00
+
+final class Util {
+
+ static OptionalInt negToDef(int value) {
+ return value < 0 ? OptionalInt.empty() : OptionalInt.of(value);
2021-11-24 18:58:26 +01:00
+ }
2021-06-11 14:02:28 +02:00
+}
2023-10-04 03:06:23 +02:00
diff --git a/src/main/java/io/papermc/paper/configuration/type/number/BelowZeroToEmpty.java b/src/main/java/io/papermc/paper/configuration/type/number/BelowZeroToEmpty.java
new file mode 100644
index 0000000000000000000000000000000000000000..31068170086aeac51a2adb952b19672e875ba528
--- /dev/null
+++ b/src/main/java/io/papermc/paper/configuration/type/number/BelowZeroToEmpty.java
@@ -0,0 +1,11 @@
+package io.papermc.paper.configuration.type.number;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface BelowZeroToEmpty {
+}
diff --git a/src/main/java/io/papermc/paper/configuration/type/number/DoubleOr.java b/src/main/java/io/papermc/paper/configuration/type/number/DoubleOr.java
new file mode 100644
index 0000000000000000000000000000000000000000..0e7205e6ba9b207082c8c530142f0b832dcd242d
2023-10-04 03:06:23 +02:00
--- /dev/null
+++ b/src/main/java/io/papermc/paper/configuration/type/number/DoubleOr.java
@@ -0,0 +1,74 @@
2023-10-04 03:06:23 +02:00
+package io.papermc.paper.configuration.type.number;
+
+import com.google.common.base.Preconditions;
+import java.util.OptionalDouble;
+import java.util.function.DoublePredicate;
2023-10-04 03:06:23 +02:00
+import java.util.function.Function;
+import java.util.function.Predicate;
+import org.spongepowered.configurate.serialize.ScalarSerializer;
+
+public interface DoubleOr {
+
+ default double or(final double fallback) {
+ return this.value().orElse(fallback);
+ }
+
+ OptionalDouble value();
+
+ default double doubleValue() {
+ return this.value().orElseThrow();
+ }
+
+ record Default(OptionalDouble value) implements DoubleOr {
+ private static final String DEFAULT_VALUE = "default";
+ public static final Default USE_DEFAULT = new Default(OptionalDouble.empty());
+ public static final ScalarSerializer<Default> SERIALIZER = new Serializer<>(Default.class, Default::new, DEFAULT_VALUE, USE_DEFAULT);
+ }
+
+ record Disabled(OptionalDouble value) implements DoubleOr {
+ private static final String DISABLED_VALUE = "disabled";
+ public static final Disabled DISABLED = new Disabled(OptionalDouble.empty());
+ public static final ScalarSerializer<Disabled> SERIALIZER = new Serializer<>(Disabled.class, Disabled::new, DISABLED_VALUE, DISABLED);
+
+ public boolean test(DoublePredicate predicate) {
+ return this.value.isPresent() && predicate.test(this.value.getAsDouble());
+ }
+
+ public boolean enabled() {
+ return this.value.isPresent();
+ }
+ }
2023-10-04 03:06:23 +02:00
+
+ final class Serializer<T extends DoubleOr> extends OptionalNumSerializer<T, OptionalDouble> {
+ Serializer(final Class<T> classOfT, final Function<OptionalDouble, T> factory, String emptySerializedValue, T emptyValue) {
+ super(classOfT, emptySerializedValue, emptyValue, OptionalDouble::empty, OptionalDouble::isEmpty, factory, double.class);
+ }
+
+ @Override
+ protected Object serialize(final T item, final Predicate<Class<?>> typeSupported) {
+ final OptionalDouble value = item.value();
+ if (value.isPresent()) {
+ return value.getAsDouble();
+ } else {
+ return this.emptySerializedValue;
+ }
+ }
+
+ @Override
+ protected OptionalDouble full(final String value) {
+ return OptionalDouble.of(Double.parseDouble(value));
+ }
+
+ @Override
+ protected OptionalDouble full(final Number num) {
+ return OptionalDouble.of(num.doubleValue());
+ }
+
+ @Override
+ protected boolean belowZero(final OptionalDouble value) {
+ Preconditions.checkArgument(value.isPresent());
+ return value.getAsDouble() < 0;
+ }
+ }
+}
+
diff --git a/src/main/java/io/papermc/paper/configuration/type/number/IntOr.java b/src/main/java/io/papermc/paper/configuration/type/number/IntOr.java
new file mode 100644
index 0000000000000000000000000000000000000000..73a7b664923121daedac8f01a26253438da68119
2023-10-04 03:06:23 +02:00
--- /dev/null
+++ b/src/main/java/io/papermc/paper/configuration/type/number/IntOr.java
@@ -0,0 +1,85 @@
2023-10-04 03:06:23 +02:00
+package io.papermc.paper.configuration.type.number;
+
+import com.google.common.base.Preconditions;
2023-10-04 03:06:23 +02:00
+import com.mojang.logging.LogUtils;
+import java.util.OptionalInt;
+import java.util.function.Function;
+import java.util.function.IntPredicate;
+import java.util.function.Predicate;
+import org.slf4j.Logger;
+import org.spongepowered.configurate.serialize.ScalarSerializer;
+
+public interface IntOr {
+
+ Logger LOGGER = LogUtils.getClassLogger();
+
+ default int or(final int fallback) {
+ return this.value().orElse(fallback);
+ }
+
+ OptionalInt value();
+
+ default boolean isDefined() {
+ return this.value().isPresent();
+ }
+
2023-10-04 03:06:23 +02:00
+ default int intValue() {
+ return this.value().orElseThrow();
+ }
+
+ record Default(OptionalInt value) implements IntOr {
+ private static final String DEFAULT_VALUE = "default";
+ public static final Default USE_DEFAULT = new Default(OptionalInt.empty());
+ public static final ScalarSerializer<Default> SERIALIZER = new Serializer<>(Default.class, Default::new, DEFAULT_VALUE, USE_DEFAULT);
+ }
+
+ record Disabled(OptionalInt value) implements IntOr {
+ private static final String DISABLED_VALUE = "disabled";
+ public static final Disabled DISABLED = new Disabled(OptionalInt.empty());
+ public static final ScalarSerializer<Disabled> SERIALIZER = new Serializer<>(Disabled.class, Disabled::new, DISABLED_VALUE, DISABLED);
+
+ public boolean test(IntPredicate predicate) {
+ return this.value.isPresent() && predicate.test(this.value.getAsInt());
+ }
+
+ public boolean enabled() {
+ return this.value.isPresent();
+ }
+ }
+
+ final class Serializer<T extends IntOr> extends OptionalNumSerializer<T, OptionalInt> {
+
+ private Serializer(Class<T> classOfT, Function<OptionalInt, T> factory, String emptySerializedValue, T emptyValue) {
+ super(classOfT, emptySerializedValue, emptyValue, OptionalInt::empty, OptionalInt::isEmpty, factory, int.class);
+ }
+
+ @Override
+ protected OptionalInt full(final String value) {
+ return OptionalInt.of(Integer.parseInt(value));
+ }
+
+ @Override
+ protected OptionalInt full(final Number num) {
+ if (num.intValue() != num.doubleValue() || num.intValue() != num.longValue()) {
+ LOGGER.error("{} cannot be converted to an integer without losing information", num);
+ }
+ return OptionalInt.of(num.intValue());
+ }
+
+ @Override
+ protected boolean belowZero(final OptionalInt value) {
+ Preconditions.checkArgument(value.isPresent());
+ return value.getAsInt() < 0;
2023-10-04 03:06:23 +02:00
+ }
+
+ @Override
+ protected Object serialize(final T item, final Predicate<Class<?>> typeSupported) {
+ final OptionalInt value = item.value();
+ if (value.isPresent()) {
+ return value.getAsInt();
+ } else {
+ return this.emptySerializedValue;
+ }
+ }
+ }
+}
diff --git a/src/main/java/io/papermc/paper/configuration/type/number/OptionalNumSerializer.java b/src/main/java/io/papermc/paper/configuration/type/number/OptionalNumSerializer.java
new file mode 100644
index 0000000000000000000000000000000000000000..614aba60bb07946a144650fd3aedb31649057ae1
--- /dev/null
+++ b/src/main/java/io/papermc/paper/configuration/type/number/OptionalNumSerializer.java
@@ -0,0 +1,58 @@
+package io.papermc.paper.configuration.type.number;
+
+import java.lang.reflect.AnnotatedType;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import org.apache.commons.lang3.math.NumberUtils;
+import org.spongepowered.configurate.serialize.ScalarSerializer;
+import org.spongepowered.configurate.serialize.SerializationException;
+
+public abstract class OptionalNumSerializer<T, O> extends ScalarSerializer.Annotated<T> {
+
+ protected final String emptySerializedValue;
+ protected final T emptyValue;
+ private final Supplier<O> empty;
+ private final Predicate<O> isEmpty;
+ private final Function<O, T> factory;
+ private final Class<?> number;
+
+ protected OptionalNumSerializer(final Class<T> classOfT, final String emptySerializedValue, final T emptyValue, final Supplier<O> empty, final Predicate<O> isEmpty, final Function<O, T> factory, final Class<?> number) {
+ super(classOfT);
+ this.emptySerializedValue = emptySerializedValue;
+ this.emptyValue = emptyValue;
+ this.empty = empty;
+ this.isEmpty = isEmpty;
+ this.factory = factory;
+ this.number = number;
+ }
+
+ @Override
+ public final T deserialize(final AnnotatedType type, final Object obj) throws SerializationException {
+ final O value;
+ if (obj instanceof String string) {
+ if (this.emptySerializedValue.equalsIgnoreCase(string)) {
+ value = this.empty.get();
+ } else if (NumberUtils.isParsable(string)) {
+ value = this.full(string);
+ } else {
+ throw new SerializationException("%s (%s) is not a(n) %s or '%s'".formatted(obj, type, this.number.getSimpleName(), this.emptySerializedValue));
+ }
+ } else if (obj instanceof Number num) {
+ value = this.full(num);
+ } else {
+ throw new SerializationException("%s (%s) is not a(n) %s or '%s'".formatted(obj, type, this.number.getSimpleName(), this.emptySerializedValue));
+ }
+ if (this.isEmpty.test(value) || (type.isAnnotationPresent(BelowZeroToEmpty.class) && this.belowZero(value))) {
+ return this.emptyValue;
+ } else {
+ return this.factory.apply(value);
+ }
+ }
+
+ protected abstract O full(final String value);
+
+ protected abstract O full(final Number num);
+
+ protected abstract boolean belowZero(O value);
+}
2021-06-11 14:02:28 +02:00
diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java
2024-10-22 19:28:57 +02:00
index fc5c42e77a76b0ca946b13435144584b9c4bfafa..9bd6056bba6ba48bada7e9cd5883b0a171b0bbc4 100644
2021-06-11 14:02:28 +02:00
--- a/src/main/java/net/minecraft/server/Main.java
+++ b/src/main/java/net/minecraft/server/Main.java
2024-04-23 20:03:16 +02:00
@@ -129,6 +129,10 @@ public class Main {
RegionFileVersion.configure(dedicatedserversettings.getProperties().regionFileComression);
2023-03-14 19:05:23 +01:00
Path path2 = Paths.get("eula.txt");
Eula eula = new Eula(path2);
+ // Paper start - load config files early 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 - load config files early for access below if needed
2021-06-11 14:02:28 +02:00
2023-03-14 19:05:23 +01:00
if (optionset.has("initSettings")) { // CraftBukkit
// CraftBukkit start - SPIGOT-5761: Create bukkit.yml and commands.yml if not present
2024-04-23 20:03:16 +02:00
@@ -163,7 +167,7 @@ public class Main {
}
2021-06-11 14:02:28 +02:00
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 - pass OptionSet to load paper config files
// 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
2024-10-22 19:28:57 +02:00
index c54fae06acd566742cea3aef537ad1b4e2b7414a..3fc0abef2c4e2c8ceb3b8c4f02c59700aa3d0803 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
2024-10-22 19:28:57 +02:00
@@ -320,6 +320,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; // Paper - add paper configuration files
public static <S extends MinecraftServer> S spin(Function<Thread, S> serverFactory) {
AtomicReference<S> atomicreference = new AtomicReference();
2024-10-22 19:28:57 +02:00
@@ -420,6 +421,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
Runtime.getRuntime().addShutdownHook(new org.bukkit.craftbukkit.util.ServerShutdownThread(this));
2023-09-21 19:39:51 +02:00
// CraftBukkit end
+ this.paperConfigurations = services.paperConfigurations(); // Paper - add paper configuration files
}
2023-09-21 19:39:51 +02:00
private void readScoreboard(DimensionDataStorage persistentStateManager) {
diff --git a/src/main/java/net/minecraft/server/Services.java b/src/main/java/net/minecraft/server/Services.java
index dfbb04800d6f1dcbb909fcdfeb1ebf1a5efa6a48..5928e5f1934b8e247ba516595018ed5c633d3b5d 100644
--- a/src/main/java/net/minecraft/server/Services.java
+++ b/src/main/java/net/minecraft/server/Services.java
@@ -10,16 +10,32 @@ import javax.annotation.Nullable;
import net.minecraft.server.players.GameProfileCache;
import net.minecraft.util.SignatureValidator;
+
public record Services(
- MinecraftSessionService sessionService, ServicesKeySet servicesKeySet, GameProfileRepository profileRepository, GameProfileCache profileCache
+ MinecraftSessionService sessionService, ServicesKeySet servicesKeySet, GameProfileRepository profileRepository, GameProfileCache profileCache, @javax.annotation.Nullable io.papermc.paper.configuration.PaperConfigurations paperConfigurations // Paper - add paper configuration files
) {
+ // Paper start - add paper configuration files
2023-06-07 18:24:39 +02:00
+ public Services(MinecraftSessionService sessionService, ServicesKeySet servicesKeySet, GameProfileRepository profileRepository, GameProfileCache profileCache) {
+ this(sessionService, servicesKeySet, profileRepository, profileCache, null);
+ }
+
+ @Override
+ public io.papermc.paper.configuration.PaperConfigurations paperConfigurations() {
+ return java.util.Objects.requireNonNull(this.paperConfigurations);
+ }
+ // Paper end - add paper configuration files
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 - add optionset to load paper config files
MinecraftSessionService minecraftSessionService = authenticationService.createMinecraftSessionService();
GameProfileRepository gameProfileRepository = authenticationService.createProfileRepository();
GameProfileCache gameProfileCache = new GameProfileCache(gameProfileRepository, new File(rootDirectory, "usercache.json"));
2023-06-07 18:24:39 +02:00
- return new Services(minecraftSessionService, authenticationService.getServicesKeySet(), gameProfileRepository, gameProfileCache);
+ // Paper start - load paper config files from cli options
+ 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"));
2023-06-07 18:24:39 +02:00
+ return new Services(minecraftSessionService, authenticationService.getServicesKeySet(), gameProfileRepository, gameProfileCache, paperConfigurations);
+ // Paper end - load paper config files from cli options
}
2023-06-07 18:24:39 +02:00
@Nullable
2021-06-11 14:02:28 +02:00
diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
2024-10-22 19:28:57 +02:00
index abde0c14bf0998830f1f9a7661e9eab8b35c7b85..ca095f9d6c985b066a393debc6529973a3616397 100644
2021-06-11 14:02:28 +02:00
--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
2024-10-22 19:28:57 +02:00
@@ -199,6 +199,10 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
2023-10-27 01:34:58 +02:00
org.spigotmc.SpigotConfig.init((java.io.File) this.options.valueOf("spigot-settings"));
2021-06-11 14:02:28 +02:00
org.spigotmc.SpigotConfig.registerCommands();
// Spigot end
+ // Paper start - initialize global and world-defaults configuration
+ this.paperConfigurations.initializeGlobalConfiguration(this.registryAccess());
+ this.paperConfigurations.initializeWorldDefaultsConfiguration(this.registryAccess());
+ // Paper end - initialize global and world-defaults configuration
2021-06-11 14:02:28 +02:00
this.setPvpAllowed(dedicatedserverproperties.pvp);
this.setFlightAllowed(dedicatedserverproperties.allowFlight);
diff --git a/src/main/java/net/minecraft/server/dedicated/Settings.java b/src/main/java/net/minecraft/server/dedicated/Settings.java
2024-04-23 20:03:16 +02:00
index 6d89a5414f46a0c30badb4fcd25bc6cb6d18db3a..0ec3b546db0cf3858dd9cd9ea067d1d6713a8491 100644
--- a/src/main/java/net/minecraft/server/dedicated/Settings.java
+++ b/src/main/java/net/minecraft/server/dedicated/Settings.java
@@ -119,6 +119,7 @@ public abstract class Settings<T extends Settings<T>> {
try {
// CraftBukkit start - Don't attempt writing to file if it's read only
if (path.toFile().exists() && !path.toFile().canWrite()) {
+ Settings.LOGGER.warn("Can not write to file {}, skipping.", path); // Paper - log message file is read-only
return;
}
// CraftBukkit end
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
2024-10-22 19:28:57 +02:00
index a0b4ca006ba51da1a91d10b8e8d4a1b12a5a37d3..a17846ccd8581c3d6da962e977623aaab8314ec7 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
2024-10-22 19:28:57 +02:00
@@ -241,7 +241,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
2023-03-14 19:05:23 +01:00
2024-10-22 19:28:57 +02:00
// Add env and gen to constructor, IWorldDataServer -> WorldDataServer
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, @Nullable RandomSequences randomsequences, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) {
- super(iworlddataserver, resourcekey, minecraftserver.registryAccess(), worlddimension.type(), false, flag, i, minecraftserver.getMaxChainedNeighborUpdates(), gen, biomeProvider, env);
+ super(iworlddataserver, resourcekey, minecraftserver.registryAccess(), worlddimension.type(), 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, minecraftserver.registryAccess(), iworlddataserver.getGameRules()))); // Paper - create paper world configs
this.pvpMode = minecraftserver.isPvpAllowed();
this.convertable = convertable_conversionsession;
this.uuid = WorldUUID.getUUID(convertable_conversionsession.levelDirectory.path().toFile());
2021-06-11 14:02:28 +02:00
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
2024-10-22 19:28:57 +02:00
index 9cf0c141fefe67893828e300cba4f8a8545ba25f..c8e49c1904c80c4ede40ca5c26efad9b12e247d1 100644
2021-06-11 14:02:28 +02:00
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
2024-10-22 19:28:57 +02:00
@@ -157,6 +157,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<>();
2021-06-11 14:02:28 +02:00
public boolean populating;
public final org.spigotmc.SpigotWorldConfig spigotConfig; // Spigot
+ // Paper start - add paper world config
+ private final io.papermc.paper.configuration.WorldConfiguration paperConfig;
+ public io.papermc.paper.configuration.WorldConfiguration paperConfig() {
+ return this.paperConfig;
+ }
+ // Paper end - add paper world config
2021-06-11 14:02:28 +02:00
public final SpigotTimings.WorldTimingsHandler timings; // Spigot
public static BlockPos lastPhysicsProblem; // Spigot
2024-10-22 19:28:57 +02:00
@@ -174,8 +180,9 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
2021-06-11 14:02:28 +02:00
public abstract ResourceKey<LevelStem> getTypeKey();
2024-10-22 19:28:57 +02:00
- protected Level(WritableLevelData worlddatamutable, ResourceKey<Level> resourcekey, RegistryAccess iregistrycustom, Holder<DimensionType> holder, 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, RegistryAccess iregistrycustom, Holder<DimensionType> holder, 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 - create paper world config
2021-06-11 14:02:28 +02:00
this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot
+ this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper - create paper world config
2021-06-11 14:02:28 +02:00
this.generator = gen;
this.world = new CraftWorld((ServerLevel) this, gen, biomeProvider, env);
2021-06-11 14:02:28 +02:00
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
2024-10-22 19:28:57 +02:00
index c48b1d5fc73a98eb4967632d8e6e0744961a688f..3882ae04173cd125fe490692a6bc2b4d8b20ff7b 100644
2021-06-11 14:02:28 +02:00
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
2024-10-22 19:28:57 +02:00
@@ -967,6 +967,7 @@ public final class CraftServer implements Server {
2021-06-11 14:02:28 +02:00
}
2023-10-27 01:34:58 +02:00
org.spigotmc.SpigotConfig.init((File) this.console.options.valueOf("spigot-settings")); // Spigot
+ this.console.paperConfigurations.reloadConfigs(this.console);
for (ServerLevel world : this.console.getAllLevels()) {
world.serverLevelData.setDifficulty(config.difficulty);
2024-10-22 19:28:57 +02:00
world.setSpawnSettings(config.spawnMonsters);
2021-06-11 14:02:28 +02:00
diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java
2024-10-22 19:28:57 +02:00
index 17e10c4373b4281cc74b748c4a1e173e36eb9196..755b1d77418ecae0dc9ec5197275d0cd914e7bee 100644
2021-06-11 14:02:28 +02:00
--- a/src/main/java/org/bukkit/craftbukkit/Main.java
+++ b/src/main/java/org/bukkit/craftbukkit/Main.java
2024-04-23 20:03:16 +02:00
@@ -142,6 +142,19 @@ public class Main {
2021-06-11 14:02:28 +02:00
.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")
2021-06-11 14:02:28 +02:00
+ .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
Updated Upstream (Bukkit/CraftBukkit/Spigot) (#11102) Upstream has released updates that appear to apply and compile correctly. This update has not been tested by PaperMC and as with ANY update, please do your own testing Bukkit Changes: 3a3bea52 SPIGOT-7829: Increase maximum outgoing plugin message size to match Vanilla intention 5cd1c8cb SPIGOT-7831: Add CreatureSpawnEvent.SpawnReason#POTION_EFFECT a8e278f0 SPIGOT-7827: Sync EntityPortalEvent with PlayerPortalEvent since non-players can now create portals 53729d12 Remove spurious ApiStatus.Internal annotation b9f57486 SPIGOT-7799, PR-1039: Expose explosion world interaction in EntityExplodeEvent and BlockExplodeEvent 7983b966 PR-1029: Trial changing a small number of inner enums to classes/interfaces to better support custom values CraftBukkit Changes: 403accd56 SPIGOT-7831: Add CreatureSpawnEvent.SpawnReason#POTION_EFFECT 812761660 Increase outdated build delay bed1e3ff6 SPIGOT-7827: Sync EntityPortalEvent with PlayerPortalEvent since non-players can now create portals 2444c8b23 SPIGOT-7823: Suspicious sand and gravel material are not marked as having gravity correctly aceddcd0b SPIGOT-7820: Enum changes - duplicate method name a0d2d6a84 SPIGOT-7813: Material#isInteractable() always returns false 8fd64b091 SPIGOT-7806: Handle both loot and inventory item drop behaviour in PlayerDeathEvent a4ee40b74 SPIGOT-7799, PR-1436: Expose explosion world interaction in EntityExplodeEvent and BlockExplodeEvent 082aa51c5 PR-1424: Trial changing a small number of inner enums to classes/interfaces to better support custom values 66e78a96b SPIGOT-7815: Consider EntityDamageEvent status for Wolf armor damage Spigot Changes: 5bbef5ad SPIGOT-7834: Modify max value for generic.max_absorption
2024-07-18 10:13:20 +02:00
index 038fd72710b3084c17d52d4cce087a5bd0aa3a01..e42677a14ec8e1a42747603fb4112822e326fb70 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 a6d2ce801b236b046b94913bccf7eccfc561f35a..8bc7a9da0eb345e65f42461e2fb22731eb80790a 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)
@@ -138,14 +144,14 @@ public class SpigotWorldConfig
public double itemMerge;
private void itemMerge()
{
- this.itemMerge = this.getDouble("merge-radius.item", 2.5 );
+ this.itemMerge = this.getDouble("merge-radius.item", 0.5 );
this.log( "Item Merge Radius: " + this.itemMerge );
}
public double expMerge;
private void expMerge()
{
- this.expMerge = this.getDouble("merge-radius.exp", 3.0 );
+ this.expMerge = this.getDouble("merge-radius.exp", -1 );
this.log( "Experience Merge Radius: " + this.expMerge );
}
@@ -197,7 +203,7 @@ public class SpigotWorldConfig
public int animalActivationRange = 32;
public int monsterActivationRange = 32;
- public int raiderActivationRange = 48;
+ public int raiderActivationRange = 64;
public int miscActivationRange = 16;
public boolean tickInactiveVillagers = true;
public boolean ignoreSpectatorActivation = false;
@@ -212,10 +218,10 @@ public class SpigotWorldConfig
this.log( "Entity Activation Range: An " + this.animalActivationRange + " / Mo " + this.monsterActivationRange + " / Ra " + this.raiderActivationRange + " / Mi " + this.miscActivationRange + " / Tiv " + this.tickInactiveVillagers + " / Isa " + this.ignoreSpectatorActivation );
}
- public int playerTrackingRange = 48;
- public int animalTrackingRange = 48;
- public int monsterTrackingRange = 48;
- public int miscTrackingRange = 32;
+ public int playerTrackingRange = 128;
+ public int animalTrackingRange = 96;
+ public int monsterTrackingRange = 96;
+ public int miscTrackingRange = 96;
public int displayTrackingRange = 128;
public int otherTrackingRange = 64;
private void trackingRange()
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);
+ }
+ }
+ }
+}
Updated Upstream (Bukkit/CraftBukkit) (#11501) Upstream has released updates that appear to apply and compile correctly. This update has not been tested by PaperMC and as with ANY update, please do your own testing Bukkit Changes: bb4e97c6 Add support for Java 23 bc6874dd Bump asm to 9.7.1 50e8a00b PR-1064: Add specific getTopInventory methods for InventoryView derivatives 758b0a0f SPIGOT-7911: Fix Location#isWorldLoaded() for re-loaded worlds 133a64a7 Improve Registry#getOrThrow messages be0f5957 PR-1058: Add tests for Minecraft registry <-> Bukkit fields d1b31df2 PR-1062: Clarify BeaconView documentation 3fab4384 PR-1060: Cache Material to BlockType and ItemType conversion 967a7301 SPIGOT-7906: Increase YAML nesting limit to 100 6ecf033d SPIGOT-7899: Smithing recipes don't require inputs CraftBukkit Changes: 0a7bd6c81 PR-1493: Improve reroute performance and add some tests 54941524c Add support for Java 23 f4d957fff SPIGOT-7915: Fix World#getKeepSpawnInMemory() using Spawn Radius rather than Spawn Chunk Radius ded183674 Fix HIDE_ENCHANTS flag in items without enchantments 308785a0a Bump asm to 9.7.1 and re-add ClassReader to ClassWriter 72ce823cd PR-1487: Add specific getTopInventory methods for InventoryView derivatives 11a5e840c SPIGOT-7907, PR-1484: Improve merchant recipe item matching behavior to more closely align with older versions 45b66f7e4 SPIGOT-7909: Always set HIDE_ENCHANTS flag to item if flag is set 963459791 Increase outdated build delay fc5b2d75f SPIGOT-7910: Fix launching breeze wind charge from API and improve dispenser launch API c7d6428f2 SPIGOT-7856, PR-1483: End platform not dropping items after replacing blocks 2a5572b52 SPIGOT-7780, PR-1482: Cannot edit chunks during unload event 527041ab5 SPIGOT-7902, PR-1477: Fix CraftMetaPotion#hasCustomEffects() does not check if customEffects (List) is empty 5529a1769 Implement base methods for tags 30fbdbaaf Improve Registry#getOrThrow messages 6b71a7322 PR-1475: Add tests for Minecraft registry <-> Bukkit fields 5f24c255c SPIGOT-7908: Mark junit-platform-suite-engine as test scope e4c92ef65 PR-1473: Change tests to use suites, to run tests in different environments and feature flags d25e1e722 PR-1481: Fix BeaconView#set[X]Effect(null) d69a05362 PR-1480: Fix PerMaterialTest#isEdible test running for legacy materials bb3284a89 PR-1479: Use custom #isBlock method in legacy init instead of the one in Material, since it relies on legacy being init 98c57cbbe SPIGOT-7904: Fix NPE for PlayerItemBreakEvent f35bae9ec Fix missing hasJukeboxPlayable 8a6f8b6d8 SPIGOT-7881: CTRL+Pick Block saves position data into item 7913b3be7 SPIGOT-7899: Smithing recipes don't require inputs
2024-10-21 00:06:54 +02:00
diff --git a/src/test/java/org/bukkit/support/DummyServerHelper.java b/src/test/java/org/bukkit/support/DummyServerHelper.java
2024-10-22 19:28:57 +02:00
index 0a6ba289a94468b67d282a199250142e1e86f075..bdfa164ea21cba91b30b965d65d47112111a1209 100644
Updated Upstream (Bukkit/CraftBukkit) (#11501) Upstream has released updates that appear to apply and compile correctly. This update has not been tested by PaperMC and as with ANY update, please do your own testing Bukkit Changes: bb4e97c6 Add support for Java 23 bc6874dd Bump asm to 9.7.1 50e8a00b PR-1064: Add specific getTopInventory methods for InventoryView derivatives 758b0a0f SPIGOT-7911: Fix Location#isWorldLoaded() for re-loaded worlds 133a64a7 Improve Registry#getOrThrow messages be0f5957 PR-1058: Add tests for Minecraft registry <-> Bukkit fields d1b31df2 PR-1062: Clarify BeaconView documentation 3fab4384 PR-1060: Cache Material to BlockType and ItemType conversion 967a7301 SPIGOT-7906: Increase YAML nesting limit to 100 6ecf033d SPIGOT-7899: Smithing recipes don't require inputs CraftBukkit Changes: 0a7bd6c81 PR-1493: Improve reroute performance and add some tests 54941524c Add support for Java 23 f4d957fff SPIGOT-7915: Fix World#getKeepSpawnInMemory() using Spawn Radius rather than Spawn Chunk Radius ded183674 Fix HIDE_ENCHANTS flag in items without enchantments 308785a0a Bump asm to 9.7.1 and re-add ClassReader to ClassWriter 72ce823cd PR-1487: Add specific getTopInventory methods for InventoryView derivatives 11a5e840c SPIGOT-7907, PR-1484: Improve merchant recipe item matching behavior to more closely align with older versions 45b66f7e4 SPIGOT-7909: Always set HIDE_ENCHANTS flag to item if flag is set 963459791 Increase outdated build delay fc5b2d75f SPIGOT-7910: Fix launching breeze wind charge from API and improve dispenser launch API c7d6428f2 SPIGOT-7856, PR-1483: End platform not dropping items after replacing blocks 2a5572b52 SPIGOT-7780, PR-1482: Cannot edit chunks during unload event 527041ab5 SPIGOT-7902, PR-1477: Fix CraftMetaPotion#hasCustomEffects() does not check if customEffects (List) is empty 5529a1769 Implement base methods for tags 30fbdbaaf Improve Registry#getOrThrow messages 6b71a7322 PR-1475: Add tests for Minecraft registry <-> Bukkit fields 5f24c255c SPIGOT-7908: Mark junit-platform-suite-engine as test scope e4c92ef65 PR-1473: Change tests to use suites, to run tests in different environments and feature flags d25e1e722 PR-1481: Fix BeaconView#set[X]Effect(null) d69a05362 PR-1480: Fix PerMaterialTest#isEdible test running for legacy materials bb3284a89 PR-1479: Use custom #isBlock method in legacy init instead of the one in Material, since it relies on legacy being init 98c57cbbe SPIGOT-7904: Fix NPE for PlayerItemBreakEvent f35bae9ec Fix missing hasJukeboxPlayable 8a6f8b6d8 SPIGOT-7881: CTRL+Pick Block saves position data into item 7913b3be7 SPIGOT-7899: Smithing recipes don't require inputs
2024-10-21 00:06:54 +02:00
--- a/src/test/java/org/bukkit/support/DummyServerHelper.java
+++ b/src/test/java/org/bukkit/support/DummyServerHelper.java
@@ -91,6 +91,7 @@ public final class DummyServerHelper {
when(instance.getPluginManager()).thenReturn(pluginManager);
// Paper end - testing additions
Updated Upstream (Bukkit/CraftBukkit) (#11501) Upstream has released updates that appear to apply and compile correctly. This update has not been tested by PaperMC and as with ANY update, please do your own testing Bukkit Changes: bb4e97c6 Add support for Java 23 bc6874dd Bump asm to 9.7.1 50e8a00b PR-1064: Add specific getTopInventory methods for InventoryView derivatives 758b0a0f SPIGOT-7911: Fix Location#isWorldLoaded() for re-loaded worlds 133a64a7 Improve Registry#getOrThrow messages be0f5957 PR-1058: Add tests for Minecraft registry <-> Bukkit fields d1b31df2 PR-1062: Clarify BeaconView documentation 3fab4384 PR-1060: Cache Material to BlockType and ItemType conversion 967a7301 SPIGOT-7906: Increase YAML nesting limit to 100 6ecf033d SPIGOT-7899: Smithing recipes don't require inputs CraftBukkit Changes: 0a7bd6c81 PR-1493: Improve reroute performance and add some tests 54941524c Add support for Java 23 f4d957fff SPIGOT-7915: Fix World#getKeepSpawnInMemory() using Spawn Radius rather than Spawn Chunk Radius ded183674 Fix HIDE_ENCHANTS flag in items without enchantments 308785a0a Bump asm to 9.7.1 and re-add ClassReader to ClassWriter 72ce823cd PR-1487: Add specific getTopInventory methods for InventoryView derivatives 11a5e840c SPIGOT-7907, PR-1484: Improve merchant recipe item matching behavior to more closely align with older versions 45b66f7e4 SPIGOT-7909: Always set HIDE_ENCHANTS flag to item if flag is set 963459791 Increase outdated build delay fc5b2d75f SPIGOT-7910: Fix launching breeze wind charge from API and improve dispenser launch API c7d6428f2 SPIGOT-7856, PR-1483: End platform not dropping items after replacing blocks 2a5572b52 SPIGOT-7780, PR-1482: Cannot edit chunks during unload event 527041ab5 SPIGOT-7902, PR-1477: Fix CraftMetaPotion#hasCustomEffects() does not check if customEffects (List) is empty 5529a1769 Implement base methods for tags 30fbdbaaf Improve Registry#getOrThrow messages 6b71a7322 PR-1475: Add tests for Minecraft registry <-> Bukkit fields 5f24c255c SPIGOT-7908: Mark junit-platform-suite-engine as test scope e4c92ef65 PR-1473: Change tests to use suites, to run tests in different environments and feature flags d25e1e722 PR-1481: Fix BeaconView#set[X]Effect(null) d69a05362 PR-1480: Fix PerMaterialTest#isEdible test running for legacy materials bb3284a89 PR-1479: Use custom #isBlock method in legacy init instead of the one in Material, since it relies on legacy being init 98c57cbbe SPIGOT-7904: Fix NPE for PlayerItemBreakEvent f35bae9ec Fix missing hasJukeboxPlayable 8a6f8b6d8 SPIGOT-7881: CTRL+Pick Block saves position data into item 7913b3be7 SPIGOT-7899: Smithing recipes don't require inputs
2024-10-21 00:06:54 +02:00
+ io.papermc.paper.configuration.GlobalConfigTestingBase.setupGlobalConfigForTest(); // Paper - configuration files - setup global configuration test base
return instance;
}
}