diff --git a/patches/server/0004-Paper-config-files.patch b/patches/server/0004-Paper-config-files.patch index 6a12b196d2..2b94fbc2ad 100644 --- a/patches/server/0004-Paper-config-files.patch +++ b/patches/server/0004-Paper-config-files.patch @@ -113,20 +113,19 @@ index 0000000000000000000000000000000000000000..7a4a7a654fe2516ed894a68f2657344d +} diff --git a/src/main/java/io/papermc/paper/configuration/Configurations.java b/src/main/java/io/papermc/paper/configuration/Configurations.java new file mode 100644 -index 0000000000000000000000000000000000000000..697a9c495a58de6e6a9a921054b5ae1bba0eb339 +index 0000000000000000000000000000000000000000..844275e8671c62633e370ae3d4e0738eedac3e7e --- /dev/null +++ b/src/main/java/io/papermc/paper/configuration/Configurations.java -@@ -0,0 +1,198 @@ +@@ -0,0 +1,285 @@ +package io.papermc.paper.configuration; + -+import com.google.common.base.Suppliers; ++import io.leangen.geantyref.TypeToken; +import io.papermc.paper.configuration.constraint.Constraint; +import io.papermc.paper.configuration.constraint.Constraints; ++import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; -+import org.apache.commons.lang3.RandomStringUtils; ++import org.checkerframework.checker.nullness.qual.Nullable; +import org.jetbrains.annotations.MustBeInvokedByOverriders; -+import org.spigotmc.SpigotConfig; -+import org.spigotmc.SpigotWorldConfig; +import org.spongepowered.configurate.CommentedConfigurationNode; +import org.spongepowered.configurate.ConfigurateException; +import org.spongepowered.configurate.ConfigurationNode; @@ -140,19 +139,16 @@ index 0000000000000000000000000000000000000000..697a9c495a58de6e6a9a921054b5ae1b +import java.lang.reflect.Type; +import java.nio.file.Files; +import java.nio.file.Path; ++import java.util.HashMap; ++import java.util.Map; ++import java.util.NoSuchElementException; +import java.util.Objects; -+import java.util.function.Supplier; +import java.util.function.UnaryOperator; + +public abstract class Configurations { + + public static final String WORLD_DEFAULTS = "__world_defaults__"; -+ private static final Supplier SPIGOT_WORLD_DEFAULTS = Suppliers.memoize(() -> new SpigotWorldConfig(RandomStringUtils.randomAlphabetic(255)) { -+ @Override // override to ensure "verbose" is false -+ public void init() { -+ SpigotConfig.readConfig(SpigotWorldConfig.class, this); -+ } -+ }); ++ public static final ResourceLocation WORLD_DEFAULTS_KEY = new ResourceLocation("configurations", WORLD_DEFAULTS); + protected final Path globalFolder; + protected final Class globalConfigClass; + protected final Class worldConfigClass; @@ -241,45 +237,56 @@ index 0000000000000000000000000000000000000000..697a9c495a58de6e6a9a921054b5ae1b + protected void applyGlobalConfigTransformations(final ConfigurationNode node) throws ConfigurateException { + } + ++ @MustBeInvokedByOverriders ++ protected ContextMap.Builder createDefaultContextMap() { ++ return ContextMap.builder() ++ .put(WORLD_NAME, WORLD_DEFAULTS) ++ .put(WORLD_KEY, WORLD_DEFAULTS_KEY); ++ } ++ + public void initializeWorldDefaultsConfiguration() throws ConfigurateException { -+ final YamlConfigurationLoader loader = this.createDefaultWorldLoader(false); ++ final ContextMap contextMap = this.createDefaultContextMap() ++ .put(FIRST_DEFAULT) ++ .build(); ++ final YamlConfigurationLoader loader = this.createDefaultWorldLoader(false, contextMap); + final ConfigurationNode node = loader.load(); -+ this.applyWorldConfigTransformations(WORLD_DEFAULTS, node); ++ this.applyWorldConfigTransformations(contextMap, node); + final W instance = node.require(this.worldConfigClass); + node.set(this.worldConfigClass, instance); + loader.save(node); + } + -+ private YamlConfigurationLoader createDefaultWorldLoader(final boolean requireFile) { ++ private YamlConfigurationLoader createDefaultWorldLoader(final boolean requireFile, final ContextMap contextMap) { + final Path configFile = this.globalFolder.resolve(this.defaultWorldConfigFileName); + if (requireFile && !Files.exists(configFile)) { + throw new IllegalStateException("World defaults configuration file '" + configFile + "' doesn't exist"); + } -+ return this.createWorldConfigLoaderBuilder(WORLD_DEFAULTS, SPIGOT_WORLD_DEFAULTS.get()) -+ .defaultOptions(this.applyObjectMapperFactory(this.createWorldObjectMapperFactoryBuilder(WORLD_DEFAULTS, SPIGOT_WORLD_DEFAULTS.get()).build())) ++ return this.createWorldConfigLoaderBuilder(contextMap) ++ .defaultOptions(this.applyObjectMapperFactory(this.createWorldObjectMapperFactoryBuilder(contextMap).build())) + .path(configFile) + .build(); + } + -+ protected ObjectMapper.Factory.Builder createWorldObjectMapperFactoryBuilder(final String levelName, final SpigotWorldConfig spigotConfig) { ++ protected ObjectMapper.Factory.Builder createWorldObjectMapperFactoryBuilder(final ContextMap contextMap) { + return this.createObjectMapper(); + } + + @MustBeInvokedByOverriders -+ protected YamlConfigurationLoader.Builder createWorldConfigLoaderBuilder(final String levelName, final SpigotWorldConfig spigotConfig) { ++ protected YamlConfigurationLoader.Builder createWorldConfigLoaderBuilder(final ContextMap contextMap) { + return this.createLoaderBuilder(); + } + + // Make sure to run version transforms on the default world config first via #setupWorldDefaultsConfig -+ public W createWorldConfig(final Path dir, final String levelName, final SpigotWorldConfig spigotConfig) throws IOException { -+ return this.createWorldConfig(dir, levelName, spigotConfig, creator(this.worldConfigClass, false)); ++ public W createWorldConfig(final ContextMap contextMap) throws IOException { ++ return this.createWorldConfig(contextMap, creator(this.worldConfigClass, false)); + } + -+ protected W createWorldConfig(final Path dir, final String levelName, final SpigotWorldConfig spigotConfig, final CheckedFunction creator) throws IOException { -+ final YamlConfigurationLoader defaultsLoader = this.createDefaultWorldLoader(true); ++ protected W createWorldConfig(final ContextMap contextMap, final CheckedFunction creator) throws IOException { ++ final YamlConfigurationLoader defaultsLoader = this.createDefaultWorldLoader(true, this.createDefaultContextMap().build()); + final ConfigurationNode defaultsNode = defaultsLoader.load(); + + boolean newFile = false; ++ final Path dir = contextMap.require(WORLD_DIRECTORY); + final Path worldConfigFile = dir.resolve(this.worldConfigFileName); + if (Files.notExists(worldConfigFile)) { + Files.createDirectories(dir); @@ -287,22 +294,25 @@ index 0000000000000000000000000000000000000000..697a9c495a58de6e6a9a921054b5ae1b + newFile = true; + } + -+ final YamlConfigurationLoader worldLoader = this.createWorldConfigLoaderBuilder(levelName, spigotConfig) -+ .defaultOptions(this.applyObjectMapperFactory(this.createWorldObjectMapperFactoryBuilder(levelName, spigotConfig).build())) ++ final YamlConfigurationLoader worldLoader = this.createWorldConfigLoaderBuilder(contextMap) ++ .defaultOptions(this.applyObjectMapperFactory(this.createWorldObjectMapperFactoryBuilder(contextMap).build())) + .path(worldConfigFile) + .build(); + final ConfigurationNode worldNode = worldLoader.load(); + if (newFile) { + worldNode.node(Configuration.VERSION_FIELD).set(WorldConfiguration.CURRENT_VERSION); + } -+ this.applyWorldConfigTransformations(levelName, worldNode); ++ this.applyWorldConfigTransformations(contextMap, worldNode); ++ this.applyDefaultsAwareWorldConfigTransformations(contextMap, worldNode, defaultsNode); + worldLoader.save(worldNode); // save before loading node NOTE: don't save the backing node after loading it, or you'll fill up the world-specific config + worldNode.mergeFrom(defaultsNode); -+ final W worldConfig = creator.apply(worldNode); -+ return worldConfig; ++ return creator.apply(worldNode); + } + -+ protected void applyWorldConfigTransformations(final String world, final ConfigurationNode node) throws ConfigurateException { ++ protected void applyWorldConfigTransformations(final ContextMap contextMap, final ConfigurationNode node) throws ConfigurateException { ++ } ++ ++ protected void applyDefaultsAwareWorldConfigTransformations(final ContextMap contextMap, final ConfigurationNode worldNode, final ConfigurationNode defaultsNode) throws ConfigurateException { + } + + private UnaryOperator applyObjectMapperFactory(final ObjectMapper.Factory factory) { @@ -314,6 +324,83 @@ index 0000000000000000000000000000000000000000..697a9c495a58de6e6a9a921054b5ae1b + public Path getWorldConfigFile(ServerLevel level) { + return level.convertable.levelDirectory.path().resolve(this.worldConfigFileName); + } ++ ++ public static class ContextMap { ++ private static final Object VOID = new Object(); ++ ++ public static Builder builder() { ++ return new Builder(); ++ } ++ ++ private final Map, Object> backingMap; ++ ++ private ContextMap(Map, Object> map) { ++ this.backingMap = Map.copyOf(map); ++ } ++ ++ @SuppressWarnings("unchecked") ++ public T require(ContextKey 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 @Nullable T get(ContextKey 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, Object> buildingMap = new HashMap<>(); ++ ++ public Builder put(ContextKey key, T value) { ++ this.buildingMap.put(key, value); ++ return this; ++ } ++ ++ public Builder put(ContextKey key) { ++ this.buildingMap.put(key, VOID); ++ return this; ++ } ++ ++ public ContextMap build() { ++ return new ContextMap(this.buildingMap); ++ } ++ } ++ } ++ ++ public static final ContextKey WORLD_DIRECTORY = new ContextKey<>(Path.class, "world directory"); ++ public static final ContextKey WORLD_NAME = new ContextKey<>(String.class, "world name"); // TODO remove when we deprecate level names ++ public static final ContextKey WORLD_KEY = new ContextKey<>(ResourceLocation.class, "world key"); ++ public static final ContextKey FIRST_DEFAULT = new ContextKey<>(Void.class, "first default"); ++ ++ public record ContextKey(TypeToken type, String name) { ++ ++ public ContextKey(Class 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 @@ -587,10 +674,10 @@ index 0000000000000000000000000000000000000000..2d04305279108f2102ebb026998d391f +} diff --git a/src/main/java/io/papermc/paper/configuration/InnerClassFieldDiscoverer.java b/src/main/java/io/papermc/paper/configuration/InnerClassFieldDiscoverer.java new file mode 100644 -index 0000000000000000000000000000000000000000..8c2acb00daf652c160904ceb977cb5547504b3c7 +index 0000000000000000000000000000000000000000..b33c7e67cff07c801d1b7aa2bc342ab5a9c1bad3 --- /dev/null +++ b/src/main/java/io/papermc/paper/configuration/InnerClassFieldDiscoverer.java -@@ -0,0 +1,112 @@ +@@ -0,0 +1,118 @@ +package io.papermc.paper.configuration; + +import org.checkerframework.checker.nullness.qual.Nullable; @@ -695,8 +782,14 @@ index 0000000000000000000000000000000000000000..8c2acb00daf652c160904ceb977cb554 + return this.overrides; + } + -+ static FieldDiscoverer worldConfig(SpigotWorldConfig spigotConfig) { -+ return new InnerClassFieldDiscoverer(Map.of(WorldConfiguration.class, new WorldConfiguration(spigotConfig))); ++ static FieldDiscoverer worldConfig(Configurations.ContextMap contextMap) { ++ final Map, Object> overrides = Map.of( ++ WorldConfiguration.class, new WorldConfiguration( ++ contextMap.require(PaperConfigurations.SPIGOT_WORLD_CONFIG_CONTEXT_KEY).get(), ++ contextMap.require(Configurations.WORLD_KEY) ++ ) ++ ); ++ return new InnerClassFieldDiscoverer(overrides); + } + + static FieldDiscoverer globalConfig() { @@ -743,16 +836,18 @@ index 0000000000000000000000000000000000000000..69add4a7f1147015806bc9b63a8340d1 +} 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..aa52663a65381f175411a37f9b0731de5ed772aa +index 0000000000000000000000000000000000000000..5069e55e46b628dc0fdaccbfc279033f60924eb8 --- /dev/null +++ b/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java -@@ -0,0 +1,323 @@ +@@ -0,0 +1,369 @@ +package io.papermc.paper.configuration; + ++import com.google.common.base.Suppliers; +import com.google.common.collect.Table; +import com.mojang.logging.LogUtils; +import io.leangen.geantyref.TypeToken; +import io.papermc.paper.configuration.legacy.RequiresSpigotInitialization; ++import io.papermc.paper.configuration.serializer.EnumValueSerializer; +import io.papermc.paper.configuration.serializer.FastutilMapSerializer; +import io.papermc.paper.configuration.serializer.PacketClassSerializer; +import io.papermc.paper.configuration.serializer.StringRepresentableSerializer; @@ -760,7 +855,9 @@ index 0000000000000000000000000000000000000000..aa52663a65381f175411a37f9b0731de +import io.papermc.paper.configuration.serializer.collections.MapSerializer; +import io.papermc.paper.configuration.serializer.registry.RegistryHolderSerializer; +import io.papermc.paper.configuration.serializer.registry.RegistryValueSerializer; ++import io.papermc.paper.configuration.transformation.Transformations; +import io.papermc.paper.configuration.transformation.global.LegacyPaperConfig; ++import io.papermc.paper.configuration.transformation.world.FeatureSeedsGeneration; +import io.papermc.paper.configuration.transformation.world.LegacyPaperWorldConfig; +import io.papermc.paper.configuration.type.BooleanOrDefault; +import io.papermc.paper.configuration.type.DoubleOrDefault; @@ -772,23 +869,26 @@ index 0000000000000000000000000000000000000000..aa52663a65381f175411a37f9b0731de +import it.unimi.dsi.fastutil.objects.Reference2LongMap; +import it.unimi.dsi.fastutil.objects.Reference2LongOpenHashMap; +import net.minecraft.core.Registry; -+import net.minecraft.data.BuiltinRegistries; ++import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; ++import org.apache.commons.lang3.RandomStringUtils; +import org.bukkit.command.Command; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.YamlConfiguration; +import org.jetbrains.annotations.VisibleForTesting; +import org.slf4j.Logger; ++import org.spigotmc.SpigotConfig; +import org.spigotmc.SpigotWorldConfig; +import org.spongepowered.configurate.BasicConfigurationNode; +import org.spongepowered.configurate.ConfigurateException; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.ConfigurationOptions; +import org.spongepowered.configurate.objectmapping.ObjectMapper; ++import org.spongepowered.configurate.transformation.ConfigurationTransformation; +import org.spongepowered.configurate.yaml.YamlConfigurationLoader; + +import java.io.File; @@ -798,8 +898,9 @@ index 0000000000000000000000000000000000000000..aa52663a65381f175411a37f9b0731de +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.HashMap; ++import java.util.List; +import java.util.Map; -+import java.util.function.UnaryOperator; ++import java.util.function.Supplier; + +import static com.google.common.base.Preconditions.checkState; +import static io.leangen.geantyref.GenericTypeReflector.erase; @@ -846,6 +947,14 @@ index 0000000000000000000000000000000000000000..aa52663a65381f175411a37f9b0731de + This is a world configuration file for Paper. + This file may start empty but can be filled with settings to override ones in the config/world-defaults.yml"""; + ++ private static final Supplier SPIGOT_WORLD_DEFAULTS = Suppliers.memoize(() -> new SpigotWorldConfig(RandomStringUtils.randomAlphabetic(255)) { ++ @Override // override to ensure "verbose" is false ++ public void init() { ++ SpigotConfig.readConfig(SpigotWorldConfig.class, this); ++ } ++ }); ++ static final ContextKey> SPIGOT_WORLD_CONFIG_CONTEXT_KEY = new ContextKey<>(new TypeToken>() {}, "spigot world config"); ++ + + public PaperConfigurations(final Path globalFolder) { + super(globalFolder, GlobalConfiguration.class, WorldConfiguration.class, GLOBAL_CONFIG_FILE_NAME, WORLD_DEFAULTS_CONFIG_FILE_NAME, WORLD_CONFIG_FILE_NAME); @@ -858,7 +967,10 @@ index 0000000000000000000000000000000000000000..aa52663a65381f175411a37f9b0731de + } + + private static ConfigurationOptions defaultOptions(ConfigurationOptions options) { -+ return options.serializers(builder -> builder.register(MapSerializer.TYPE, new MapSerializer())); ++ return options.serializers(builder -> builder ++ .register(MapSerializer.TYPE, new MapSerializer(false)) ++ .register(new EnumValueSerializer()) ++ ); + } + + @Override @@ -890,23 +1002,24 @@ index 0000000000000000000000000000000000000000..aa52663a65381f175411a37f9b0731de + } + + @Override -+ protected void applyGlobalConfigTransformations(org.spongepowered.configurate.ConfigurationNode node) throws org.spongepowered.configurate.ConfigurateException { -+ super.applyGlobalConfigTransformations(node); ++ protected ContextMap.Builder createDefaultContextMap() { ++ return super.createDefaultContextMap() ++ .put(SPIGOT_WORLD_CONFIG_CONTEXT_KEY, SPIGOT_WORLD_DEFAULTS); + } + + @Override -+ protected ObjectMapper.Factory.Builder createWorldObjectMapperFactoryBuilder(final String levelName, final SpigotWorldConfig spigotConfig) { -+ return super.createWorldObjectMapperFactoryBuilder(levelName, spigotConfig) -+ .addNodeResolver(new RequiresSpigotInitialization.Factory(spigotConfig)) ++ protected ObjectMapper.Factory.Builder createWorldObjectMapperFactoryBuilder(final ContextMap contextMap) { ++ return super.createWorldObjectMapperFactoryBuilder(contextMap) ++ .addNodeResolver(new RequiresSpigotInitialization.Factory(contextMap.require(SPIGOT_WORLD_CONFIG_CONTEXT_KEY).get())) + .addNodeResolver(new NestedSetting.Factory()) -+ .addDiscoverer(InnerClassFieldDiscoverer.worldConfig(spigotConfig)); ++ .addDiscoverer(InnerClassFieldDiscoverer.worldConfig(contextMap)); + } + + @Override -+ protected YamlConfigurationLoader.Builder createWorldConfigLoaderBuilder(final String levelName, final SpigotWorldConfig spigotConfig) { -+ return super.createWorldConfigLoaderBuilder(levelName, spigotConfig) ++ protected YamlConfigurationLoader.Builder createWorldConfigLoaderBuilder(final ContextMap contextMap) { ++ return super.createWorldConfigLoaderBuilder(contextMap) + .defaultOptions(options -> options -+ .header(levelName.equals(WORLD_DEFAULTS) ? WORLD_DEFAULTS_HEADER : WORLD_HEADER) ++ .header(contextMap.require(WORLD_NAME).equals(WORLD_DEFAULTS) ? WORLD_DEFAULTS_HEADER : WORLD_HEADER) + .serializers(serializers -> serializers + .register(new TypeToken>() {}, new FastutilMapSerializer.SomethingToPrimitive>(Reference2IntOpenHashMap::new, Integer.TYPE)) + .register(new TypeToken>() {}, new FastutilMapSerializer.SomethingToPrimitive>(Reference2LongOpenHashMap::new, Long.TYPE)) @@ -916,18 +1029,19 @@ index 0000000000000000000000000000000000000000..aa52663a65381f175411a37f9b0731de + .register(DoubleOrDefault.SERIALIZER) + .register(BooleanOrDefault.SERIALIZER) + .register(Duration.SERIALIZER) -+ .register(FallbackValueSerializer.create(spigotConfig, MinecraftServer::getServer)) -+ .register(new RegistryValueSerializer<>(new TypeToken>() {}, Registry.ENTITY_TYPE, true)) -+ .register(new RegistryValueSerializer<>(Item.class, Registry.ITEM, true)) -+ .register(new RegistryHolderSerializer<>(new TypeToken>() {}, BuiltinRegistries.CONFIGURED_FEATURE, false)) -+ .register(new RegistryHolderSerializer<>(Item.class, Registry.ITEM, true)) ++ .register(FallbackValueSerializer.create(contextMap.require(SPIGOT_WORLD_CONFIG_CONTEXT_KEY).get(), MinecraftServer::getServer)) ++ .register(new RegistryValueSerializer<>(new TypeToken>() {}, Registry.ENTITY_TYPE_REGISTRY, true)) ++ .register(new RegistryValueSerializer<>(Item.class, Registry.ITEM_REGISTRY, true)) ++ .register(new RegistryHolderSerializer<>(new TypeToken>() {}, Registry.CONFIGURED_FEATURE_REGISTRY, false)) ++ .register(new RegistryHolderSerializer<>(Item.class, Registry.ITEM_REGISTRY, true)) + ) + ); + } + + @Override -+ protected void applyWorldConfigTransformations(final String world, final ConfigurationNode node) throws ConfigurateException { ++ protected void applyWorldConfigTransformations(final ContextMap contextMap, final ConfigurationNode node) throws ConfigurateException { + final ConfigurationNode version = node.node(Configuration.VERSION_FIELD); ++ final String world = contextMap.require(WORLD_NAME); + if (version.virtual()) { + LOGGER.warn("The world config file for " + world + " didn't have a version set, assuming latest"); + version.raw(WorldConfiguration.CURRENT_VERSION); @@ -935,10 +1049,22 @@ index 0000000000000000000000000000000000000000..aa52663a65381f175411a37f9b0731de + // ADD FUTURE TRANSFORMS HERE + } + ++ private static final List DEFAULT_AWARE_TRANSFORMATIONS = List.of(FeatureSeedsGeneration::apply); ++ + @Override -+ public WorldConfiguration createWorldConfig(final Path dir, final String levelName, final SpigotWorldConfig spigotConfig) { ++ 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); ++ } ++ ++ @Override ++ public WorldConfiguration createWorldConfig(final ContextMap contextMap) { ++ final String levelName = contextMap.require(WORLD_NAME); + try { -+ return super.createWorldConfig(dir, levelName, spigotConfig); ++ return super.createWorldConfig(contextMap); + } catch (IOException exception) { + throw new RuntimeException("Could not create world config for " + levelName, exception); + } @@ -954,13 +1080,26 @@ index 0000000000000000000000000000000000000000..aa52663a65381f175411a37f9b0731de + this.initializeGlobalConfiguration(reloader(this.globalConfigClass, GlobalConfiguration.get())); + this.initializeWorldDefaultsConfiguration(); + for (ServerLevel level : server.getAllLevels()) { -+ this.createWorldConfig(level.convertable.levelDirectory.path(), level.serverLevelData.getLevelName(), level.spigotConfig, reloader(this.worldConfigClass, level.paperConfig())); ++ this.createWorldConfig(createWorldContextMap(level), reloader(this.worldConfigClass, level.paperConfig())); + } + } catch (Exception ex) { + throw new RuntimeException("Could not reload paper configuration files", ex); + } + } + ++ private static ContextMap createWorldContextMap(ServerLevel level) { ++ return createWorldContextMap(level.convertable.levelDirectory.path(), level.serverLevelData.getLevelName(), level.dimension().location(), level.spigotConfig); ++ } ++ ++ public static ContextMap createWorldContextMap(Path dir, String levelName, ResourceLocation worldKey, SpigotWorldConfig spigotConfig) { ++ return ContextMap.builder() ++ .put(WORLD_DIRECTORY, dir) ++ .put(WORLD_NAME, levelName) ++ .put(WORLD_KEY, worldKey) ++ .put(SPIGOT_WORLD_CONFIG_CONTEXT_KEY, Suppliers.ofInstance(spigotConfig)) ++ .build(); ++ } ++ + public static PaperConfigurations setup(final Path legacyConfig, final Path configDir, final Path worldFolder, final File spigotConfig) throws Exception { + if (needsConverting(legacyConfig)) { + try { @@ -1072,10 +1211,10 @@ index 0000000000000000000000000000000000000000..aa52663a65381f175411a37f9b0731de +} 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..163f02b4bf34da712b30166e80d859a96bf8d911 +index 0000000000000000000000000000000000000000..4a1a7e9764dc6f64cc2968baf5958d0cde87cbe2 --- /dev/null +++ b/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java -@@ -0,0 +1,470 @@ +@@ -0,0 +1,465 @@ +package io.papermc.paper.configuration; + +import com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray; @@ -1087,6 +1226,7 @@ index 0000000000000000000000000000000000000000..163f02b4bf34da712b30166e80d859a9 +import io.papermc.paper.configuration.legacy.MaxEntityCollisionsInitializer; +import io.papermc.paper.configuration.legacy.RequiresSpigotInitialization; +import io.papermc.paper.configuration.legacy.SpawnLoadedRangeInitializer; ++import io.papermc.paper.configuration.transformation.world.FeatureSeedsGeneration; +import io.papermc.paper.configuration.type.BooleanOrDefault; +import io.papermc.paper.configuration.type.DoubleOrDefault; +import io.papermc.paper.configuration.type.Duration; @@ -1100,6 +1240,7 @@ index 0000000000000000000000000000000000000000..163f02b4bf34da712b30166e80d859a9 +import net.minecraft.Util; +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; ++import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.Difficulty; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.MobCategory; @@ -1127,8 +1268,14 @@ index 0000000000000000000000000000000000000000..163f02b4bf34da712b30166e80d859a9 + static final int CURRENT_VERSION = 28; + + private transient final SpigotWorldConfig spigotConfig; -+ WorldConfiguration(SpigotWorldConfig spigotConfig) { ++ private transient final ResourceLocation worldKey; ++ WorldConfiguration(SpigotWorldConfig spigotConfig, ResourceLocation worldKey) { + this.spigotConfig = spigotConfig; ++ this.worldKey = worldKey; ++ } ++ ++ public boolean isDefault() { ++ return this.worldKey.equals(PaperConfigurations.WORLD_DEFAULTS_KEY); + } + + @Setting(Configuration.VERSION_FIELD) @@ -1499,31 +1646,18 @@ index 0000000000000000000000000000000000000000..163f02b4bf34da712b30166e80d859a9 + public Table, String, Integer> behavior = Util.make(HashBasedTable.create(), table -> table.put(EntityType.VILLAGER, "validatenearbypoi", -1)); + } + ++ @Setting(FeatureSeedsGeneration.FEATURE_SEEDS_KEY) + public FeatureSeeds featureSeeds; + + public class FeatureSeeds extends ConfigurationPart.Post { ++ @Setting(FeatureSeedsGeneration.GENERATE_KEY) + public boolean generateRandomSeedsForAll = false; ++ @Setting(FeatureSeedsGeneration.FEATURES_KEY) + public Reference2LongMap>> features = new Reference2LongOpenHashMap<>(); + + @Override + public void postProcess() { -+ features.defaultReturnValue(-1); -+ if (generateRandomSeedsForAll) { -+ final java.util.Random random = new java.security.SecureRandom(); -+ boolean added[] = {false}; -+ net.minecraft.server.MinecraftServer.getServer().registryAccess().registry(Registry.CONFIGURED_FEATURE_REGISTRY).get().holders().forEach(holder -> { -+ if (features.containsKey(holder)) { -+ return; -+ } -+ -+ final long seed = random.nextLong(); -+ features.put(holder, seed); -+ added[0] = true; -+ }); -+ if (added[0]) { -+ LOGGER.info("Generated random feature seeds."); -+ } -+ } ++ this.features.defaultReturnValue(-1); + } + } + @@ -1584,15 +1718,17 @@ index 0000000000000000000000000000000000000000..514be9a11e2ca368ea72dd2bac1b84bf +} diff --git a/src/main/java/io/papermc/paper/configuration/constraint/Constraints.java b/src/main/java/io/papermc/paper/configuration/constraint/Constraints.java new file mode 100644 -index 0000000000000000000000000000000000000000..57f8765a827aeab592e952875b4ed8f503b1f757 +index 0000000000000000000000000000000000000000..b470332f542c30c42355adb711ff148e8e1dd7a1 --- /dev/null +++ b/src/main/java/io/papermc/paper/configuration/constraint/Constraints.java -@@ -0,0 +1,68 @@ +@@ -0,0 +1,74 @@ +package io.papermc.paper.configuration.constraint; + ++import com.mojang.logging.LogUtils; +import io.papermc.paper.configuration.GlobalConfiguration; +import io.papermc.paper.configuration.type.DoubleOrDefault; +import org.checkerframework.checker.nullness.qual.Nullable; ++import org.slf4j.Logger; +import org.spongepowered.configurate.objectmapping.meta.Constraint; +import org.spongepowered.configurate.serialize.SerializationException; + @@ -1609,10 +1745,14 @@ index 0000000000000000000000000000000000000000..57f8765a827aeab592e952875b4ed8f5 + } + + public static final class Velocity implements Constraint { ++ ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ + @Override + public void validate(final GlobalConfiguration.Proxies.@Nullable Velocity value) throws SerializationException { + if (value != null && value.enabled && value.secret.isEmpty()) { -+ throw new SerializationException("Velocity is enabled, but no secret key was specified. A secret key is required!"); ++ LOGGER.error("Velocity is enabled, but no secret key was specified. A secret key is required. Disabling velocity..."); ++ value.enabled = false; + } + } + } @@ -1793,6 +1933,62 @@ index 0000000000000000000000000000000000000000..4e3bcd7c478096384fcc643d48771ab9 +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/EnumValueSerializer.java b/src/main/java/io/papermc/paper/configuration/serializer/EnumValueSerializer.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2afb9268447792e3cdb46172b2050dbce066c59a +--- /dev/null ++++ b/src/main/java/io/papermc/paper/configuration/serializer/EnumValueSerializer.java +@@ -0,0 +1,50 @@ ++package io.papermc.paper.configuration.serializer; ++ ++import com.mojang.logging.LogUtils; ++import io.leangen.geantyref.TypeToken; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.slf4j.Logger; ++import org.spongepowered.configurate.serialize.ScalarSerializer; ++import org.spongepowered.configurate.serialize.SerializationException; ++import org.spongepowered.configurate.util.EnumLookup; ++ ++import java.lang.reflect.Type; ++import java.util.Arrays; ++import java.util.List; ++import java.util.function.Predicate; ++ ++import static io.leangen.geantyref.GenericTypeReflector.erase; ++ ++/** ++ * Enum serializer that lists options if fails and accepts `-` as `_`. ++ */ ++public class EnumValueSerializer extends ScalarSerializer> { ++ ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ ++ public EnumValueSerializer() { ++ super(new TypeToken>() {}); ++ } ++ ++ @SuppressWarnings({"rawtypes", "unchecked"}) ++ @Override ++ public @Nullable Enum deserialize(final Type type, final Object obj) throws SerializationException { ++ final String enumConstant = obj.toString(); ++ final Class 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 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> typeSupported) { ++ return item.name(); ++ } ++} diff --git a/src/main/java/io/papermc/paper/configuration/serializer/FastutilMapSerializer.java b/src/main/java/io/papermc/paper/configuration/serializer/FastutilMapSerializer.java new file mode 100644 index 0000000000000000000000000000000000000000..f2f362883d1825084c277608c791f82165828ebe @@ -2081,15 +2277,14 @@ index 0000000000000000000000000000000000000000..0b235ebe6e79d7aa420d6b8a52aedb3a +} 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..0de8542c0cfd37b788b624b1a196acb7e28afd4c +index 0000000000000000000000000000000000000000..f5c0fb018b7f8eff1d6ca1f0425409adac242180 --- /dev/null +++ b/src/main/java/io/papermc/paper/configuration/serializer/collections/MapSerializer.java -@@ -0,0 +1,129 @@ +@@ -0,0 +1,148 @@ +package io.papermc.paper.configuration.serializer.collections; + +import com.mojang.logging.LogUtils; +import io.leangen.geantyref.TypeToken; -+import io.papermc.paper.configuration.Configuration; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.slf4j.Logger; +import org.spongepowered.configurate.BasicConfigurationNode; @@ -2118,6 +2313,12 @@ index 0000000000000000000000000000000000000000..0de8542c0cfd37b788b624b1a196acb7 + + private static final Logger LOGGER = LogUtils.getLogger(); + ++ private final boolean clearInvalids; ++ ++ public MapSerializer(boolean clearInvalids) { ++ this.clearInvalids = clearInvalids; ++ } ++ + @Override + public Map deserialize(Type type, ConfigurationNode node) throws SerializationException { + final Map map = new LinkedHashMap<>(); @@ -2155,8 +2356,9 @@ index 0000000000000000000000000000000000000000..0de8542c0cfd37b788b624b1a196acb7 + private @Nullable Object deserialize(Type type, TypeSerializer serializer, String mapPart, ConfigurationNode node, NodePath path) { + try { + return serializer.deserialize(type, node); -+ } catch (SerializationException exception) { -+ LOGGER.error("Could not deserialize {} {} into {} at {}", mapPart, node.raw(), type, path, exception); ++ } catch (SerializationException ex) { ++ ex.initPath(node::path); ++ LOGGER.error("Could not deserialize {} {} into {} at {}", mapPart, node.raw(), type, path); + } + return null; + } @@ -2185,6 +2387,13 @@ index 0000000000000000000000000000000000000000..0de8542c0cfd37b788b624b1a196acb7 + if (obj == null || obj.isEmpty()) { + node.set(Collections.emptyMap()); + } else { ++ final Set 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())) { @@ -2193,6 +2402,12 @@ index 0000000000000000000000000000000000000000..0de8542c0cfd37b788b624b1a196acb7 + final Object keyObj = requireNonNull(keyNode.raw(), "Key must not be null!"); + final ConfigurationNode child = node.node(keyObj); + serialize(value, valueSerializer, ent.getValue(), "value", child, child.path()); ++ unvisitedKeys.remove(keyObj); ++ } ++ if (this.clearInvalids) { ++ for (Object unusedChild : unvisitedKeys) { ++ node.removeChild(unusedChild); ++ } + } + } + } @@ -2204,7 +2419,7 @@ index 0000000000000000000000000000000000000000..0de8542c0cfd37b788b624b1a196acb7 + return true; + } catch (SerializationException ex) { + ex.initPath(node::path); -+ LOGGER.error("Could not serialize {} {} from {} at {}", mapPart, object, type, path, ex); ++ LOGGER.error("Could not serialize {} {} from {} at {}", mapPart, object, type, path); + } + return false; + } @@ -2216,16 +2431,17 @@ index 0000000000000000000000000000000000000000..0de8542c0cfd37b788b624b1a196acb7 +} diff --git a/src/main/java/io/papermc/paper/configuration/serializer/registry/RegistryEntrySerializer.java b/src/main/java/io/papermc/paper/configuration/serializer/registry/RegistryEntrySerializer.java new file mode 100644 -index 0000000000000000000000000000000000000000..aa4dc13878567732f514344eab5fcd73d0a63238 +index 0000000000000000000000000000000000000000..0e4e0f1788cf67312cb52bd572784c2f27db71b6 --- /dev/null +++ b/src/main/java/io/papermc/paper/configuration/serializer/registry/RegistryEntrySerializer.java -@@ -0,0 +1,61 @@ +@@ -0,0 +1,62 @@ +package io.papermc.paper.configuration.serializer.registry; + +import io.leangen.geantyref.TypeToken; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; ++import net.minecraft.server.MinecraftServer; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.configurate.serialize.ScalarSerializer; +import org.spongepowered.configurate.serialize.SerializationException; @@ -2235,23 +2451,23 @@ index 0000000000000000000000000000000000000000..aa4dc13878567732f514344eab5fcd73 + +abstract class RegistryEntrySerializer extends ScalarSerializer { + -+ private final Registry registry; ++ private final ResourceKey> registryKey; + private final boolean omitMinecraftNamespace; + -+ protected RegistryEntrySerializer(TypeToken type, Registry registry, boolean omitMinecraftNamespace) { ++ protected RegistryEntrySerializer(TypeToken type, ResourceKey> registryKey, boolean omitMinecraftNamespace) { + super(type); -+ this.registry = registry; ++ this.registryKey = registryKey; + this.omitMinecraftNamespace = omitMinecraftNamespace; + } + -+ protected RegistryEntrySerializer(Class type, Registry registry, boolean omitMinecraftNamespace) { ++ protected RegistryEntrySerializer(Class type, ResourceKey> registryKey, boolean omitMinecraftNamespace) { + super(type); -+ this.registry = registry; ++ this.registryKey = registryKey; + this.omitMinecraftNamespace = omitMinecraftNamespace; + } + + protected final Registry registry() { -+ return this.registry; ++ return MinecraftServer.getServer().registryAccess().registryOrThrow(this.registryKey); + } + + protected abstract T convertFromResourceKey(ResourceKey key) throws SerializationException; @@ -2278,12 +2494,12 @@ index 0000000000000000000000000000000000000000..aa4dc13878567732f514344eab5fcd73 + if (key == null) { + throw new SerializationException("Could not create a key from " + input); + } -+ return ResourceKey.create(this.registry.key(), key); ++ return ResourceKey.create(this.registryKey, key); + } +} diff --git a/src/main/java/io/papermc/paper/configuration/serializer/registry/RegistryHolderSerializer.java b/src/main/java/io/papermc/paper/configuration/serializer/registry/RegistryHolderSerializer.java new file mode 100644 -index 0000000000000000000000000000000000000000..bfac00959d7fc6a1ddabfb5a975d163537bdbbdb +index 0000000000000000000000000000000000000000..c03c1f277ff8167e8b3e4bfa0f4dfc86834f82f3 --- /dev/null +++ b/src/main/java/io/papermc/paper/configuration/serializer/registry/RegistryHolderSerializer.java @@ -0,0 +1,34 @@ @@ -2302,12 +2518,12 @@ index 0000000000000000000000000000000000000000..bfac00959d7fc6a1ddabfb5a975d1635 +public final class RegistryHolderSerializer extends RegistryEntrySerializer, T> { + + @SuppressWarnings("unchecked") -+ public RegistryHolderSerializer(TypeToken typeToken, Registry registry, boolean omitMinecraftNamespace) { -+ super((TypeToken>) TypeToken.get(TypeFactory.parameterizedClass(Holder.class, typeToken.getType())), registry, omitMinecraftNamespace); ++ public RegistryHolderSerializer(TypeToken typeToken, ResourceKey> registryKey, boolean omitMinecraftNamespace) { ++ super((TypeToken>) TypeToken.get(TypeFactory.parameterizedClass(Holder.class, typeToken.getType())), registryKey, omitMinecraftNamespace); + } + -+ public RegistryHolderSerializer(Class type, Registry registry, boolean omitMinecraftNamespace) { -+ this(TypeToken.get(type), registry, omitMinecraftNamespace); ++ public RegistryHolderSerializer(Class type, ResourceKey> registryKey, boolean omitMinecraftNamespace) { ++ this(TypeToken.get(type), registryKey, omitMinecraftNamespace); + Preconditions.checkArgument(type.getTypeParameters().length == 0, "%s must have 0 type parameters", type); + } + @@ -2323,7 +2539,7 @@ index 0000000000000000000000000000000000000000..bfac00959d7fc6a1ddabfb5a975d1635 +} diff --git a/src/main/java/io/papermc/paper/configuration/serializer/registry/RegistryValueSerializer.java b/src/main/java/io/papermc/paper/configuration/serializer/registry/RegistryValueSerializer.java new file mode 100644 -index 0000000000000000000000000000000000000000..87a79b67186987ca4d02e4f7789c329f425a5a67 +index 0000000000000000000000000000000000000000..10d3dd361cd26dc849ebd53c1235aa8e4f7af04d --- /dev/null +++ b/src/main/java/io/papermc/paper/configuration/serializer/registry/RegistryValueSerializer.java @@ -0,0 +1,34 @@ @@ -2339,12 +2555,12 @@ index 0000000000000000000000000000000000000000..87a79b67186987ca4d02e4f7789c329f + */ +public final class RegistryValueSerializer extends RegistryEntrySerializer { + -+ public RegistryValueSerializer(TypeToken type, Registry registry, boolean omitMinecraftNamespace) { -+ super(type, registry, omitMinecraftNamespace); ++ public RegistryValueSerializer(TypeToken type, ResourceKey> registryKey, boolean omitMinecraftNamespace) { ++ super(type, registryKey, omitMinecraftNamespace); + } + -+ public RegistryValueSerializer(Class type, Registry registry, boolean omitMinecraftNamespace) { -+ super(type, registry, omitMinecraftNamespace); ++ public RegistryValueSerializer(Class type, ResourceKey> registryKey, boolean omitMinecraftNamespace) { ++ super(type, registryKey, omitMinecraftNamespace); + } + + @Override @@ -2363,12 +2579,14 @@ index 0000000000000000000000000000000000000000..87a79b67186987ca4d02e4f7789c329f +} 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..c1b787a48c1197f64fa753e1a254d17198806a96 +index 0000000000000000000000000000000000000000..0300fb1e09d41465e4a50bfdc987b9571289d399 --- /dev/null +++ b/src/main/java/io/papermc/paper/configuration/transformation/Transformations.java -@@ -0,0 +1,28 @@ +@@ -0,0 +1,35 @@ +package io.papermc.paper.configuration.transformation; + ++import io.papermc.paper.configuration.Configurations; ++import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.NodePath; +import org.spongepowered.configurate.transformation.ConfigurationTransformation; + @@ -2394,13 +2612,18 @@ index 0000000000000000000000000000000000000000..c1b787a48c1197f64fa753e1a254d171 + return newPath; + }); + } ++ ++ @FunctionalInterface ++ public interface DefaultsAware { ++ void apply(final ConfigurationTransformation.Builder builder, final Configurations.ContextMap contextMap, final ConfigurationNode defaultsNode); ++ } +} diff --git a/src/main/java/io/papermc/paper/configuration/transformation/global/LegacyPaperConfig.java b/src/main/java/io/papermc/paper/configuration/transformation/global/LegacyPaperConfig.java new file mode 100644 -index 0000000000000000000000000000000000000000..ecea36a434e0f4893899ee4694166768ded1e7c2 +index 0000000000000000000000000000000000000000..9866cffcec3fdff4f9abc616d4374591d8d13860 --- /dev/null +++ b/src/main/java/io/papermc/paper/configuration/transformation/global/LegacyPaperConfig.java -@@ -0,0 +1,184 @@ +@@ -0,0 +1,190 @@ +package io.papermc.paper.configuration.transformation.global; + +import com.mojang.logging.LogUtils; @@ -2408,6 +2631,7 @@ index 0000000000000000000000000000000000000000..ecea36a434e0f4893899ee4694166768 +import io.papermc.paper.configuration.serializer.PacketClassSerializer; +import io.papermc.paper.util.ObfHelper; +import net.minecraft.network.protocol.Packet; ++import net.minecraft.network.protocol.game.ServerboundPlaceRecipePacket; +import org.bukkit.configuration.file.YamlConfiguration; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.slf4j.Logger; @@ -2523,7 +2747,12 @@ index 0000000000000000000000000000000000000000..ecea36a434e0f4893899ee4694166768 + newPath[path.size() - 1] = packet.getSimpleName(); + return newPath; + } else { -+ LOGGER.warn("Could not convert spigot-mapped packet class names because no mappings were found in the jar"); ++ final @Nullable Object keyValue = value.key(); ++ if (keyValue != null && keyValue.toString().equals("PacketPlayInAutoRecipe")) { // add special case to catch the default ++ return path.with(path.size() - 1, ServerboundPlaceRecipePacket.class.getSimpleName()).array(); ++ } else { ++ LOGGER.warn("Could not convert spigot-mapped packet class name {} because no mappings were found in the jar", keyValue); ++ } + } + return null; + }).addAction(path("loggers"), TransformAction.rename("logging")); @@ -2585,12 +2814,89 @@ index 0000000000000000000000000000000000000000..ecea36a434e0f4893899ee4694166768 + }); + } +} +diff --git a/src/main/java/io/papermc/paper/configuration/transformation/world/FeatureSeedsGeneration.java b/src/main/java/io/papermc/paper/configuration/transformation/world/FeatureSeedsGeneration.java +new file mode 100644 +index 0000000000000000000000000000000000000000..75f612b04f872d0d014fdc40b07c15116857587b +--- /dev/null ++++ b/src/main/java/io/papermc/paper/configuration/transformation/world/FeatureSeedsGeneration.java +@@ -0,0 +1,71 @@ ++package io.papermc.paper.configuration.transformation.world; ++ ++import com.mojang.logging.LogUtils; ++import io.leangen.geantyref.TypeToken; ++import io.papermc.paper.configuration.Configurations; ++import it.unimi.dsi.fastutil.objects.Reference2LongMap; ++import it.unimi.dsi.fastutil.objects.Reference2LongOpenHashMap; ++import net.minecraft.core.Holder; ++import net.minecraft.core.Registry; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.slf4j.Logger; ++import org.spongepowered.configurate.ConfigurateException; ++import org.spongepowered.configurate.ConfigurationNode; ++import org.spongepowered.configurate.NodePath; ++import org.spongepowered.configurate.transformation.ConfigurationTransformation; ++import org.spongepowered.configurate.transformation.TransformAction; ++ ++import java.security.SecureRandom; ++import java.util.Objects; ++import java.util.Random; ++import java.util.concurrent.atomic.AtomicInteger; ++ ++import static org.spongepowered.configurate.NodePath.path; ++ ++public class FeatureSeedsGeneration implements TransformAction { ++ ++ public static final String FEATURE_SEEDS_KEY = "feature-seeds"; ++ public static final String GENERATE_KEY = "generate-random-seeds-for-all"; ++ public static final String FEATURES_KEY = "features"; ++ ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ ++ private final ResourceLocation worldKey; ++ ++ private FeatureSeedsGeneration(ResourceLocation worldKey) { ++ this.worldKey = worldKey; ++ } ++ ++ @Override ++ public Object @Nullable [] visitPath(NodePath path, ConfigurationNode value) throws ConfigurateException { ++ ConfigurationNode featureNode = value.node(FEATURE_SEEDS_KEY, FEATURES_KEY); ++ final Reference2LongMap>> features = Objects.requireNonNullElseGet(featureNode.get(new TypeToken>>>() {}), Reference2LongOpenHashMap::new); ++ final Random random = new SecureRandom(); ++ AtomicInteger counter = new AtomicInteger(0); ++ MinecraftServer.getServer().registryAccess().registryOrThrow(Registry.CONFIGURED_FEATURE_REGISTRY).holders().forEach(holder -> { ++ if (features.containsKey(holder)) { ++ return; ++ } ++ ++ final long seed = random.nextLong(); ++ features.put(holder, seed); ++ counter.incrementAndGet(); ++ }); ++ if (counter.get() > 0) { ++ LOGGER.info("Generated {} random feature seeds for {}", counter.get(), this.worldKey); ++ featureNode.raw(null); ++ featureNode.set(new TypeToken>>>() {}, features); ++ } ++ return null; ++ } ++ ++ ++ public static void apply(final ConfigurationTransformation.Builder builder, final Configurations.ContextMap contextMap, final ConfigurationNode defaultsNode) { ++ if (!contextMap.isDefaultWorldContext() && defaultsNode.node(FEATURE_SEEDS_KEY, GENERATE_KEY).getBoolean(false)) { ++ builder.addAction(path(), new FeatureSeedsGeneration(contextMap.require(Configurations.WORLD_KEY))); ++ } ++ } ++} diff --git a/src/main/java/io/papermc/paper/configuration/transformation/world/LegacyPaperWorldConfig.java b/src/main/java/io/papermc/paper/configuration/transformation/world/LegacyPaperWorldConfig.java new file mode 100644 -index 0000000000000000000000000000000000000000..371e5939a643b820a35e9b7d7021c883f31c3f66 +index 0000000000000000000000000000000000000000..a779e2e4875f55761a573f0db9e9d0fd177d6567 --- /dev/null +++ b/src/main/java/io/papermc/paper/configuration/transformation/world/LegacyPaperWorldConfig.java -@@ -0,0 +1,289 @@ +@@ -0,0 +1,313 @@ +package io.papermc.paper.configuration.transformation.world; + +import io.papermc.paper.configuration.Configuration; @@ -2606,7 +2912,9 @@ index 0000000000000000000000000000000000000000..371e5939a643b820a35e9b7d7021c883 +import org.spongepowered.configurate.transformation.ConfigurationTransformation; +import org.spongepowered.configurate.transformation.TransformAction; + ++import java.util.HashMap; +import java.util.List; ++import java.util.Map; +import java.util.Optional; + +import static io.papermc.paper.configuration.transformation.Transformations.moveFromRoot; @@ -2691,7 +2999,7 @@ index 0000000000000000000000000000000000000000..371e5939a643b820a35e9b7d7021c883 + String itemName = path.get(path.size() - 1).toString(); + final Optional> item = Registry.ITEM.getHolder(ResourceKey.create(Registry.ITEM_REGISTRY, new ResourceLocation(itemName))); + if (item.isEmpty()) { -+ itemName = Material.valueOf(itemName).getKey().toString(); ++ itemName = Material.valueOf(itemName).getKey().getKey().toString(); + } + final Object[] newPath = path.array(); + newPath[newPath.length - 1] = itemName; @@ -2699,7 +3007,7 @@ index 0000000000000000000000000000000000000000..371e5939a643b820a35e9b7d7021c883 + }).build()) + .addVersion(27, ConfigurationTransformation.builder().addAction(path("use-faster-eigencraft-redstone"), (path, value) -> { + final WorldConfiguration.Misc.RedstoneImplementation redstoneImplementation = value.getBoolean(false) ? WorldConfiguration.Misc.RedstoneImplementation.EIGENCRAFT : WorldConfiguration.Misc.RedstoneImplementation.VANILLA; -+ value.raw(redstoneImplementation); ++ value.set(redstoneImplementation); + final Object[] newPath = path.array(); + newPath[newPath.length - 1] = "redstone-implementation"; + return newPath; @@ -2716,6 +3024,27 @@ index 0000000000000000000000000000000000000000..371e5939a643b820a35e9b7d7021c883 + value.node("loot-tables").set(prevValue); + return path.with(path.size() - 1, "treasure-maps-find-already-discovered").array(); + }) ++ .addAction(path("alt-item-despawn-rate"), (path, value) -> { ++ if (value.isMap()) { ++ Map rebuild = new HashMap<>(); ++ value.childrenMap().forEach((key, node) -> { ++ String itemName = key.toString(); ++ final Optional> itemHolder = Registry.ITEM.getHolder(ResourceKey.create(Registry.ITEM_REGISTRY, new ResourceLocation(itemName))); ++ 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(); + } + @@ -2753,7 +3082,7 @@ index 0000000000000000000000000000000000000000..371e5939a643b820a35e9b7d7021c883 + moveFromRoot(builder, "all-chunks-are-slime-chunks", "entities", "spawning"); + moveFromRoot(builder, "skeleton-horse-thunder-spawn-chance", "entities", "spawning"); + moveFromRoot(builder, "iron-golems-can-spawn-in-air", "entities", "spawning"); -+ moveFromRoot(builder, "alt-item-despawn-rate", "entities", "spawning"); ++ moveFromRoot(builder, "alt-item-despawn-rate", "entities", "spawning"); // TODO versioned migration is broken, fix it here + 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"); @@ -2822,6 +3151,7 @@ index 0000000000000000000000000000000000000000..371e5939a643b820a35e9b7d7021c883 + moveFromRoot(builder, "remove-corrupt-tile-entities", "fixes"); + moveFromRoot(builder, "split-overstacked-loot", "fixes"); + moveFromRoot(builder, "tnt-entity-height-nerf", "fixes"); ++ moveFromRoot(builder, "fix-wither-targeting-bug", "fixes"); + moveFromGameMechanics(builder, "disable-unloaded-chunk-enderpearl-exploit", "fixes"); + moveFromGameMechanics(builder, "fix-curing-zombie-villager-discount-exploit", "fixes"); + @@ -3538,7 +3868,7 @@ index 570db14d930e15a96621d0d24ce11a27dc38494b..297bb691759ed5be375f49441778892e this.setPvpAllowed(dedicatedserverproperties.pvp); this.setFlightAllowed(dedicatedserverproperties.allowFlight); diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 5f7965076b14a694f644bf8fef9ba4f7b7a473ad..2aed985d76cb845c6e55d36d6536bcb2cb6dfa07 100644 +index 5f7965076b14a694f644bf8fef9ba4f7b7a473ad..d9287cdcbd488a87832724feb7614b142a426aa3 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java @@ -225,7 +225,7 @@ public class ServerLevel extends Level implements WorldGenLevel { @@ -3546,7 +3876,7 @@ index 5f7965076b14a694f644bf8fef9ba4f7b7a473ad..2aed985d76cb845c6e55d36d6536bcb2 // Holder holder = worlddimension.typeHolder(); // CraftBukkit - decompile error // Objects.requireNonNull(minecraftserver); // CraftBukkit - decompile error - super(iworlddataserver, resourcekey, worlddimension.typeHolder(), minecraftserver::getProfiler, false, flag, i, minecraftserver.getMaxChainedNeighborUpdates(), gen, biomeProvider, env); -+ super(iworlddataserver, resourcekey, worlddimension.typeHolder(), minecraftserver::getProfiler, false, flag, i, minecraftserver.getMaxChainedNeighborUpdates(), gen, biomeProvider, env, spigotConfig -> minecraftserver.paperConfigurations.createWorldConfig(convertable_conversionsession.levelDirectory.path(), iworlddataserver.getLevelName(), spigotConfig)); // Paper ++ super(iworlddataserver, resourcekey, worlddimension.typeHolder(), minecraftserver::getProfiler, false, flag, i, minecraftserver.getMaxChainedNeighborUpdates(), gen, biomeProvider, env, spigotConfig -> minecraftserver.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(convertable_conversionsession.levelDirectory.path(), iworlddataserver.getLevelName(), resourcekey.location(), spigotConfig))); // Paper this.pvpMode = minecraftserver.isPvpAllowed(); this.convertable = convertable_conversionsession; this.uuid = WorldUUID.getUUID(convertable_conversionsession.levelDirectory.path().toFile()); diff --git a/patches/server/0009-Paper-command.patch b/patches/server/0009-Paper-command.patch index 7e7b8fd9a1..7b534538cf 100644 --- a/patches/server/0009-Paper-command.patch +++ b/patches/server/0009-Paper-command.patch @@ -297,17 +297,17 @@ index 0000000000000000000000000000000000000000..e75134a6aa92c86aa86430c7603e1112 + } +} diff --git a/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java b/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java -index aa52663a65381f175411a37f9b0731de5ed772aa..d9f5c4154516d136c0e3488be07c8df84cb3ea35 100644 +index 5069e55e46b628dc0fdaccbfc279033f60924eb8..41b332f878810df0611137be79e7dd1a3372ec00 100644 --- a/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java +++ b/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java @@ -1,5 +1,6 @@ package io.papermc.paper.configuration; +import com.destroystokyo.paper.PaperCommand; + import com.google.common.base.Suppliers; import com.google.common.collect.Table; import com.mojang.logging.LogUtils; - import io.leangen.geantyref.TypeToken; -@@ -281,6 +282,7 @@ public class PaperConfigurations extends Configurations COMMANDS = new HashMap<>(); static { diff --git a/patches/server/0011-Paper-Metrics.patch b/patches/server/0011-Paper-Metrics.patch index c0915892b0..260a4aea8d 100644 --- a/patches/server/0011-Paper-Metrics.patch +++ b/patches/server/0011-Paper-Metrics.patch @@ -690,7 +690,7 @@ index 0000000000000000000000000000000000000000..5a19e30a9b7e65a70f68a429b8ca741f + } +} diff --git a/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java b/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java -index d9f5c4154516d136c0e3488be07c8df84cb3ea35..e279559cd8929642d80eea89b9a89d7ebe982586 100644 +index 41b332f878810df0611137be79e7dd1a3372ec00..b49e7949337517dcf1dd0738b3f8573a04efcae5 100644 --- a/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java +++ b/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java @@ -1,5 +1,6 @@ @@ -698,9 +698,9 @@ index d9f5c4154516d136c0e3488be07c8df84cb3ea35..e279559cd8929642d80eea89b9a89d7e +import com.destroystokyo.paper.Metrics; import com.destroystokyo.paper.PaperCommand; + import com.google.common.base.Suppliers; import com.google.common.collect.Table; - import com.mojang.logging.LogUtils; -@@ -281,6 +282,7 @@ public class PaperConfigurations extends Configurations COMMANDS = new HashMap<>(); @@ -708,7 +708,7 @@ index d9f5c4154516d136c0e3488be07c8df84cb3ea35..e279559cd8929642d80eea89b9a89d7e static { COMMANDS.put("paper", new PaperCommand("paper")); } -@@ -289,6 +291,11 @@ public class PaperConfigurations extends Configurations { server.server.getCommandMap().register(s, "Paper", command); }); diff --git a/patches/server/0353-Anti-Xray.patch b/patches/server/0353-Anti-Xray.patch index 2f17e65dc9..ba088ffa00 100644 --- a/patches/server/0353-Anti-Xray.patch +++ b/patches/server/0353-Anti-Xray.patch @@ -1017,7 +1017,7 @@ index 0000000000000000000000000000000000000000..80a2dfb266ae1221680a7b24fee2f7e2 + } +} diff --git a/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java b/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java -index e279559cd8929642d80eea89b9a89d7ebe982586..7a2d1cf4343f8bb4c0682b0ab9bb87957d8b3284 100644 +index b49e7949337517dcf1dd0738b3f8573a04efcae5..0ee70d37ac22da84f671b688863ff691961ad652 100644 --- a/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java +++ b/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java @@ -2,6 +2,7 @@ package io.papermc.paper.configuration; @@ -1025,17 +1025,17 @@ index e279559cd8929642d80eea89b9a89d7ebe982586..7a2d1cf4343f8bb4c0682b0ab9bb8795 import com.destroystokyo.paper.Metrics; import com.destroystokyo.paper.PaperCommand; +import com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray; + import com.google.common.base.Suppliers; import com.google.common.collect.Table; import com.mojang.logging.LogUtils; - import io.leangen.geantyref.TypeToken; -@@ -169,6 +170,7 @@ public class PaperConfigurations extends Configurations(new TypeToken>() {}, Registry.ENTITY_TYPE, true)) - .register(new RegistryValueSerializer<>(Item.class, Registry.ITEM, true)) + .register(FallbackValueSerializer.create(contextMap.require(SPIGOT_WORLD_CONFIG_CONTEXT_KEY).get(), MinecraftServer::getServer)) + .register(new RegistryValueSerializer<>(new TypeToken>() {}, Registry.ENTITY_TYPE_REGISTRY, true)) + .register(new RegistryValueSerializer<>(Item.class, Registry.ITEM_REGISTRY, true)) diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java index 7cf356a700e47686e093e2f2f880af919dc0414a..e902b437ee089907b34ae30c0a6bdf1d42e1e674 100644 --- a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java @@ -1173,15 +1173,15 @@ index c4af6e0f38ac9271247ed657b8ee6b48822417b5..7996247c00bf6ea4399322d089821432 List list = Lists.newArrayList(); List list1 = Lists.newArrayList(); diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 25df2a2a14aeae69b0156d041159c75f3e8e6eb7..7e08260e9cdb88ff122eaf9b494f908296d99fbb 100644 +index 0b8260bd1e18a332063fc0f5ffcf2cf804fdad9c..9e80a76b4d6b6c1b9b0fa99ac8fbacf101e54a79 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java @@ -401,7 +401,7 @@ public class ServerLevel extends Level implements WorldGenLevel { public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) { // Holder holder = worlddimension.typeHolder(); // CraftBukkit - decompile error // Objects.requireNonNull(minecraftserver); // CraftBukkit - decompile error -- super(iworlddataserver, resourcekey, worlddimension.typeHolder(), minecraftserver::getProfiler, false, flag, i, minecraftserver.getMaxChainedNeighborUpdates(), gen, biomeProvider, env, spigotConfig -> minecraftserver.paperConfigurations.createWorldConfig(convertable_conversionsession.levelDirectory.path(), iworlddataserver.getLevelName(), spigotConfig)); // Paper -+ super(iworlddataserver, resourcekey, worlddimension.typeHolder(), minecraftserver::getProfiler, false, flag, i, minecraftserver.getMaxChainedNeighborUpdates(), gen, biomeProvider, env, spigotConfig -> minecraftserver.paperConfigurations.createWorldConfig(convertable_conversionsession.levelDirectory.path(), iworlddataserver.getLevelName(), spigotConfig), executor); // Paper - Async-Anti-Xray - Pass executor +- super(iworlddataserver, resourcekey, worlddimension.typeHolder(), minecraftserver::getProfiler, false, flag, i, minecraftserver.getMaxChainedNeighborUpdates(), gen, biomeProvider, env, spigotConfig -> minecraftserver.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(convertable_conversionsession.levelDirectory.path(), iworlddataserver.getLevelName(), resourcekey.location(), spigotConfig))); // Paper ++ super(iworlddataserver, resourcekey, worlddimension.typeHolder(), minecraftserver::getProfiler, false, flag, i, minecraftserver.getMaxChainedNeighborUpdates(), gen, biomeProvider, env, spigotConfig -> minecraftserver.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(convertable_conversionsession.levelDirectory.path(), iworlddataserver.getLevelName(), resourcekey.location(), spigotConfig)), executor); // Paper - Async-Anti-Xray - Pass executor this.pvpMode = minecraftserver.isPvpAllowed(); this.convertable = convertable_conversionsession; this.uuid = WorldUUID.getUUID(convertable_conversionsession.levelDirectory.path().toFile()); diff --git a/patches/server/0373-Add-tick-times-API-and-mspt-command.patch b/patches/server/0373-Add-tick-times-API-and-mspt-command.patch index 72c1eb8b88..dcd7832ca5 100644 --- a/patches/server/0373-Add-tick-times-API-and-mspt-command.patch +++ b/patches/server/0373-Add-tick-times-API-and-mspt-command.patch @@ -110,7 +110,7 @@ index 0000000000000000000000000000000000000000..874f0c2a071994c2145848886caa385e + } +} diff --git a/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java b/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java -index 7a2d1cf4343f8bb4c0682b0ab9bb87957d8b3284..e8c8b9ff56f64fa63387d21c5fcab69915ff9770 100644 +index 0ee70d37ac22da84f671b688863ff691961ad652..339ff4962cf8f9f9b308245d8c8de2c5d31bf51a 100644 --- a/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java +++ b/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java @@ -1,5 +1,6 @@ @@ -120,7 +120,7 @@ index 7a2d1cf4343f8bb4c0682b0ab9bb87957d8b3284..e8c8b9ff56f64fa63387d21c5fcab699 import com.destroystokyo.paper.Metrics; import com.destroystokyo.paper.PaperCommand; import com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray; -@@ -287,6 +288,7 @@ public class PaperConfigurations extends Configurations