diff --git a/patches/server/0005-Paper-config-files.patch b/patches/server/0005-Paper-config-files.patch index be5f0c62d0..7ad8e77177 100644 --- a/patches/server/0005-Paper-config-files.patch +++ b/patches/server/0005-Paper-config-files.patch @@ -15,14 +15,14 @@ public net.minecraft.server.dedicated.DedicatedServerProperties reload(Lnet/mine public net.minecraft.world.level.NaturalSpawner SPAWNING_CATEGORIES diff --git a/build.gradle.kts b/build.gradle.kts -index e03e01c4b9f3886a72c569d7a64da0cca5cbcc5b..7c0b13c12f8f8ab43cc4128dc90101f527482a0d 100644 +index e03e01c4b9f3886a72c569d7a64da0cca5cbcc5b..ded8824a43c99754ea12544930b416c610bd2ead 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,6 +13,7 @@ dependencies { implementation("org.ow2.asm:asm:9.5") implementation("org.ow2.asm:asm-commons:9.5") // Paper - ASM event executor generation testImplementation("org.mockito:mockito-core:4.9.0") // Paper - switch to mockito -+ implementation("org.spongepowered:configurate-yaml:4.1.2") // Paper - config files ++ implementation("org.spongepowered:configurate-yaml:4.2.0-SNAPSHOT") // Paper - config files implementation("commons-lang:commons-lang:2.6") runtimeOnly("org.xerial:sqlite-jdbc:3.42.0.1") runtimeOnly("com.mysql:mysql-connector-j:8.1.0") @@ -108,19 +108,16 @@ index 0000000000000000000000000000000000000000..227039a6c69c4c99bbd9c674b3aab0ef +} diff --git a/src/main/java/io/papermc/paper/configuration/ConfigurationPart.java b/src/main/java/io/papermc/paper/configuration/ConfigurationPart.java new file mode 100644 -index 0000000000000000000000000000000000000000..7a4a7a654fe2516ed894a68f2657344df9d70f4c +index 0000000000000000000000000000000000000000..042478cf7ce150f1f1bc5cddd7fa40f86ec773dd --- /dev/null +++ b/src/main/java/io/papermc/paper/configuration/ConfigurationPart.java -@@ -0,0 +1,10 @@ +@@ -0,0 +1,7 @@ +package io.papermc.paper.configuration; + -+abstract class ConfigurationPart { -+ -+ public static abstract class Post extends ConfigurationPart { -+ -+ public abstract void postProcess(); -+ } -+ ++/** ++ * 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 @@ -441,23 +438,25 @@ index 0000000000000000000000000000000000000000..9ef6712c70fcd8912a79f3f61e351aac +} diff --git a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java new file mode 100644 -index 0000000000000000000000000000000000000000..dcfc085d355410723dbeef369579e1875a7a997b +index 0000000000000000000000000000000000000000..73a34b4e378e6012a01c8ac8b092248298be6648 --- /dev/null +++ b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -@@ -0,0 +1,293 @@ +@@ -0,0 +1,309 @@ +package io.papermc.paper.configuration; + +import co.aikar.timings.MinecraftTimings; -+import io.papermc.paper.configuration.constraint.Constraint; ++import com.mojang.logging.LogUtils; +import io.papermc.paper.configuration.constraint.Constraints; -+import io.papermc.paper.configuration.type.IntOr; ++import io.papermc.paper.configuration.type.number.IntOr; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ServerboundPlaceRecipePacket; +import org.checkerframework.checker.nullness.qual.Nullable; ++import org.slf4j.Logger; +import org.spongepowered.configurate.objectmapping.ConfigSerializable; +import org.spongepowered.configurate.objectmapping.meta.Comment; ++import org.spongepowered.configurate.objectmapping.meta.PostProcess; +import org.spongepowered.configurate.objectmapping.meta.Required; +import org.spongepowered.configurate.objectmapping.meta.Setting; + @@ -467,7 +466,8 @@ index 0000000000000000000000000000000000000000..dcfc085d355410723dbeef369579e187 + +@SuppressWarnings({"CanBeFinal", "FieldCanBeLocal", "FieldMayBeFinal", "NotNullFieldNotInitialized", "InnerClassMayBeStatic"}) +public class GlobalConfiguration extends ConfigurationPart { -+ static final int CURRENT_VERSION = 29; ++ 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): + private static GlobalConfiguration instance; + public static GlobalConfiguration get() { + return instance; @@ -495,9 +495,11 @@ index 0000000000000000000000000000000000000000..dcfc085d355410723dbeef369579e187 + public boolean useDisplayNameInQuitMessage = false; + } + ++ @Deprecated(forRemoval = true) + public Timings timings; + -+ public class Timings extends ConfigurationPart.Post { ++ @Deprecated(forRemoval = true) ++ public class Timings extends ConfigurationPart { + public boolean enabled = true; + public boolean verbose = true; + public String url = "https://timings.aikar.co/"; @@ -510,8 +512,8 @@ index 0000000000000000000000000000000000000000..dcfc085d355410723dbeef369579e187 + public int historyLength = 3600; + public String serverName = "Unknown Server"; + -+ @Override -+ public void postProcess() { ++ @PostProcess ++ private void postProcess() { + MinecraftTimings.processConfig(this); + } + } @@ -525,13 +527,20 @@ index 0000000000000000000000000000000000000000..dcfc085d355410723dbeef369579e187 + public boolean onlineMode = true; + } + -+ @Constraint(Constraints.Velocity.class) + public Velocity velocity; + + public class Velocity extends ConfigurationPart { + public boolean enabled = false; + public boolean onlineMode = false; + public String secret = ""; ++ ++ @PostProcess ++ private void postProcess() { ++ if (this.enabled && this.secret.isEmpty()) { ++ 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() { @@ -622,16 +631,17 @@ index 0000000000000000000000000000000000000000..dcfc085d355410723dbeef369579e187 + public boolean saveEmptyScoreboardTeams = false; + } + ++ @SuppressWarnings("unused") // used in postProcess + public ChunkSystem chunkSystem; + -+ public class ChunkSystem extends ConfigurationPart.Post { ++ public class ChunkSystem extends ConfigurationPart { + + public int ioThreads = -1; + public int workerThreads = -1; + public String genParallelism = "default"; + -+ @Override -+ public void postProcess() { ++ @PostProcess ++ private void postProcess() { + io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.init(this); + } + } @@ -708,13 +718,16 @@ index 0000000000000000000000000000000000000000..dcfc085d355410723dbeef369579e187 + public Misc misc; + + public class Misc extends ConfigurationPart { -+ public class ChatThreads extends ConfigurationPart.Post { ++ ++ @SuppressWarnings("unused") // used in postProcess ++ public ChatThreads chatThreads; ++ public class ChatThreads extends ConfigurationPart { + private int chatExecutorCoreSize = -1; + private int chatExecutorMaxSize = -1; + -+ @Override -+ public void postProcess() { -+ // TODO: FILL ++ @PostProcess ++ private void postProcess() { ++ // TODO: fill in separate patch + } + } + public int maxJoinsPerTick = 5; @@ -738,179 +751,6 @@ index 0000000000000000000000000000000000000000..dcfc085d355410723dbeef369579e187 + public boolean disableMushroomBlockUpdates = false; + } +} -diff --git a/src/main/java/io/papermc/paper/configuration/InnerClassFieldDiscoverer.java b/src/main/java/io/papermc/paper/configuration/InnerClassFieldDiscoverer.java -new file mode 100644 -index 0000000000000000000000000000000000000000..a0aa1f1a7adf986d500a2135aa42e138aa3c4f08 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/configuration/InnerClassFieldDiscoverer.java -@@ -0,0 +1,142 @@ -+package io.papermc.paper.configuration; -+ -+import io.leangen.geantyref.GenericTypeReflector; -+import org.checkerframework.checker.nullness.qual.Nullable; -+import org.spongepowered.configurate.objectmapping.FieldDiscoverer; -+import org.spongepowered.configurate.serialize.SerializationException; -+import org.spongepowered.configurate.util.CheckedSupplier; -+ -+import java.lang.reflect.AnnotatedType; -+import java.lang.reflect.Constructor; -+import java.lang.reflect.Field; -+import java.lang.reflect.Modifier; -+import java.util.Collections; -+import java.util.HashMap; -+import java.util.Iterator; -+import java.util.Map; -+ -+import static io.leangen.geantyref.GenericTypeReflector.erase; -+ -+final class InnerClassFieldDiscoverer implements FieldDiscoverer> { -+ -+ private final Map, Object> instanceMap = new HashMap<>(); -+ private final Map, Object> overrides; -+ @SuppressWarnings("unchecked") -+ private final FieldDiscoverer> delegate = (FieldDiscoverer>) FieldDiscoverer.object(target -> { -+ final Class type = erase(target.getType()); -+ if (this.overrides().containsKey(type)) { -+ this.instanceMap.put(type, this.overrides().get(type)); -+ return () -> this.overrides().get(type); -+ } -+ if (ConfigurationPart.class.isAssignableFrom(type) && !this.instanceMap.containsKey(type)) { -+ try { -+ final Constructor constructor; -+ final CheckedSupplier instanceSupplier; -+ if (type.getEnclosingClass() != null && !Modifier.isStatic(type.getModifiers())) { -+ final @Nullable Object instance = this.instanceMap.get(type.getEnclosingClass()); -+ if (instance == null) { -+ throw new SerializationException("Cannot create a new instance of an inner class " + type.getName() + " without an instance of its enclosing class " + type.getEnclosingClass().getName()); -+ } -+ constructor = type.getDeclaredConstructor(type.getEnclosingClass()); -+ instanceSupplier = () -> constructor.newInstance(instance); -+ } else { -+ constructor = type.getDeclaredConstructor(); -+ instanceSupplier = constructor::newInstance; -+ } -+ constructor.setAccessible(true); -+ final Object instance = instanceSupplier.get(); -+ this.instanceMap.put(type, instance); -+ return () -> instance; -+ } catch (ReflectiveOperationException e) { -+ throw new SerializationException(ConfigurationPart.class, target + " must be a valid ConfigurationPart", e); -+ } -+ } else { -+ throw new SerializationException(target + " must be a valid ConfigurationPart"); -+ } -+ }, "Object must be a unique ConfigurationPart"); -+ -+ InnerClassFieldDiscoverer(Map, Object> overrides) { -+ this.overrides = overrides; -+ } -+ -+ @Override -+ public @Nullable InstanceFactory> discover(AnnotatedType target, FieldCollector, V> collector) throws SerializationException { -+ final Class clazz = erase(target.getType()); -+ if (ConfigurationPart.class.isAssignableFrom(clazz)) { -+ final FieldDiscoverer.@Nullable InstanceFactory> instanceFactoryDelegate = this.delegate.discover(target, (name, type, annotations, deserializer, serializer) -> { -+ if (!erase(type.getType()).equals(clazz.getEnclosingClass())) { // don't collect synth fields for inner classes -+ collector.accept(name, type, annotations, deserializer, serializer); -+ } -+ }); -+ if (instanceFactoryDelegate instanceof FieldDiscoverer.MutableInstanceFactory> mutableInstanceFactoryDelegate) { -+ return new MutableInstanceFactory<>() { -+ @Override -+ public Map begin() { -+ return mutableInstanceFactoryDelegate.begin(); -+ } -+ -+ @SuppressWarnings("unchecked") -+ @Override -+ public void complete(Object instance, Map intermediate) throws SerializationException { -+ final Iterator> iter = intermediate.entrySet().iterator(); -+ try { -+ while (iter.hasNext()) { // manually merge any mergeable maps -+ Map.Entry 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 existingMap = (Map) entry.getKey().get(instance); -+ if (existingMap != null) { -+ existingMap.putAll(map); -+ } else { -+ entry.getKey().set(instance, entry.getValue()); -+ } -+ } -+ } -+ } catch (final IllegalAccessException e) { -+ throw new SerializationException(target.getType(), e); -+ } -+ mutableInstanceFactoryDelegate.complete(instance, intermediate); -+ } -+ -+ @Override -+ public Object complete(Map intermediate) throws SerializationException { -+ @Nullable Object targetInstance = InnerClassFieldDiscoverer.this.instanceMap.get(GenericTypeReflector.erase(target.getType())); -+ if (targetInstance != null) { -+ this.complete(targetInstance, intermediate); -+ } else { -+ targetInstance = mutableInstanceFactoryDelegate.complete(intermediate); -+ } -+ if (targetInstance instanceof ConfigurationPart.Post post) { -+ post.postProcess(); -+ } -+ return targetInstance; -+ } -+ -+ @Override -+ public boolean canCreateInstances() { -+ return mutableInstanceFactoryDelegate.canCreateInstances(); -+ } -+ }; -+ } -+ } -+ return null; -+ } -+ -+ private Map, Object> overrides() { -+ return this.overrides; -+ } -+ -+ 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() { -+ return new InnerClassFieldDiscoverer(Collections.emptyMap()); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/configuration/MergeMap.java b/src/main/java/io/papermc/paper/configuration/MergeMap.java -new file mode 100644 -index 0000000000000000000000000000000000000000..a977b80cb196b7345bdfcb0b65ee2021f112efd1 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/configuration/MergeMap.java -@@ -0,0 +1,19 @@ -+package io.papermc.paper.configuration; -+ -+import java.lang.annotation.Documented; -+import java.lang.annotation.ElementType; -+import java.lang.annotation.Retention; -+import java.lang.annotation.RetentionPolicy; -+import java.lang.annotation.Target; -+ -+/** -+ * For use in maps inside {@link ConfigurationPart}s that have default keys that shouldn't be removed by users -+ *

-+ * Note that when the config is reloaded, the maps will be merged again, so make sure this map can't accumulate -+ * keys overtime. -+ */ -+@Documented -+@Target(ElementType.FIELD) -+@Retention(RetentionPolicy.RUNTIME) -+public @interface MergeMap { -+} diff --git a/src/main/java/io/papermc/paper/configuration/NestedSetting.java b/src/main/java/io/papermc/paper/configuration/NestedSetting.java new file mode 100644 index 0000000000000000000000000000000000000000..69add4a7f1147015806bc9b63a8340d1893356c1 @@ -951,10 +791,10 @@ 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..9b5c2abaa28fa60cedd9f0111e5eb018f93a0561 +index 0000000000000000000000000000000000000000..9e8b8de907654050c51400286af971caca87d6bd --- /dev/null +++ b/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java -@@ -0,0 +1,450 @@ +@@ -0,0 +1,457 @@ +package io.papermc.paper.configuration; + +import com.google.common.base.Suppliers; @@ -962,14 +802,15 @@ index 0000000000000000000000000000000000000000..9b5c2abaa28fa60cedd9f0111e5eb018 +import com.mojang.logging.LogUtils; +import io.leangen.geantyref.TypeToken; +import io.papermc.paper.configuration.legacy.RequiresSpigotInitialization; ++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.collections.FastutilMapSerializer; +import io.papermc.paper.configuration.serializer.NbtPathSerializer; +import io.papermc.paper.configuration.serializer.PacketClassSerializer; +import io.papermc.paper.configuration.serializer.StringRepresentableSerializer; -+import io.papermc.paper.configuration.serializer.collections.TableSerializer; ++import io.papermc.paper.configuration.serializer.collections.FastutilMapSerializer; +import io.papermc.paper.configuration.serializer.collections.MapSerializer; ++import io.papermc.paper.configuration.serializer.collections.TableSerializer; +import io.papermc.paper.configuration.serializer.registry.RegistryHolderSerializer; +import io.papermc.paper.configuration.serializer.registry.RegistryValueSerializer; +import io.papermc.paper.configuration.transformation.Transformations; @@ -980,16 +821,25 @@ index 0000000000000000000000000000000000000000..9b5c2abaa28fa60cedd9f0111e5eb018 +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.type.BooleanOrDefault; -+import io.papermc.paper.configuration.type.DoubleOrDefault; +import io.papermc.paper.configuration.type.Duration; +import io.papermc.paper.configuration.type.DurationOrDisabled; +import io.papermc.paper.configuration.type.EngineMode; -+import io.papermc.paper.configuration.type.IntOr; ++import io.papermc.paper.configuration.type.number.DoubleOr; ++import io.papermc.paper.configuration.type.number.IntOr; +import io.papermc.paper.configuration.type.fallback.FallbackValueSerializer; +import it.unimi.dsi.fastutil.objects.Reference2IntMap; +import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.Reference2LongMap; +import it.unimi.dsi.fastutil.objects.Reference2LongOpenHashMap; ++import 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.registries.Registries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; @@ -1014,16 +864,6 @@ index 0000000000000000000000000000000000000000..9b5c2abaa28fa60cedd9f0111e5eb018 +import org.spongepowered.configurate.transformation.TransformAction; +import org.spongepowered.configurate.yaml.YamlConfigurationLoader; + -+import java.io.File; -+import java.io.IOException; -+import java.lang.reflect.Type; -+import java.nio.file.Files; -+import java.nio.file.Path; -+import java.nio.file.StandardCopyOption; -+import java.util.List; -+import java.util.function.Function; -+import java.util.function.Supplier; -+ +import static com.google.common.base.Preconditions.checkState; +import static io.leangen.geantyref.GenericTypeReflector.erase; + @@ -1093,7 +933,7 @@ index 0000000000000000000000000000000000000000..9b5c2abaa28fa60cedd9f0111e5eb018 + SpigotConfig.readConfig(SpigotWorldConfig.class, this); + } + }); -+ static final ContextKey> SPIGOT_WORLD_CONFIG_CONTEXT_KEY = new ContextKey<>(new TypeToken>() {}, "spigot world config"); ++ public static final ContextKey> SPIGOT_WORLD_CONFIG_CONTEXT_KEY = new ContextKey<>(new TypeToken>() {}, "spigot world config"); + + + public PaperConfigurations(final Path globalFolder) { @@ -1156,7 +996,14 @@ index 0000000000000000000000000000000000000000..9b5c2abaa28fa60cedd9f0111e5eb018 + return super.createWorldObjectMapperFactoryBuilder(contextMap) + .addNodeResolver(new RequiresSpigotInitialization.Factory(contextMap.require(SPIGOT_WORLD_CONFIG_CONTEXT_KEY).get())) + .addNodeResolver(new NestedSetting.Factory()) -+ .addDiscoverer(InnerClassFieldDiscoverer.worldConfig(contextMap)); ++ .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 @@ -1171,7 +1018,7 @@ index 0000000000000000000000000000000000000000..9b5c2abaa28fa60cedd9f0111e5eb018 + .register(StringRepresentableSerializer::isValidFor, new StringRepresentableSerializer()) + .register(IntOr.Default.SERIALIZER) + .register(IntOr.Disabled.SERIALIZER) -+ .register(DoubleOrDefault.SERIALIZER) ++ .register(DoubleOr.Default.SERIALIZER) + .register(BooleanOrDefault.SERIALIZER) + .register(Duration.SERIALIZER) + .register(DurationOrDisabled.SERIALIZER) @@ -1490,30 +1337,30 @@ index 0000000000000000000000000000000000000000..f0d4ec73bc8872a85e34f5c6b4d342e7 +} 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..456156e5f4adbe383a611bc81cebd0446ac042f7 +index 0000000000000000000000000000000000000000..c9ae7e88afb1ca8349a118c6b491a1e1e83517a7 --- /dev/null +++ b/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java -@@ -0,0 +1,536 @@ +@@ -0,0 +1,539 @@ +package io.papermc.paper.configuration; + +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.Table; +import com.mojang.logging.LogUtils; -+import io.papermc.paper.configuration.constraint.Constraint; -+import io.papermc.paper.configuration.constraint.Constraints; +import io.papermc.paper.configuration.legacy.MaxEntityCollisionsInitializer; +import io.papermc.paper.configuration.legacy.RequiresSpigotInitialization; +import io.papermc.paper.configuration.legacy.SpawnLoadedRangeInitializer; ++import io.papermc.paper.configuration.mapping.MergeMap; +import io.papermc.paper.configuration.serializer.NbtPathSerializer; +import io.papermc.paper.configuration.transformation.world.FeatureSeedsGeneration; +import io.papermc.paper.configuration.type.BooleanOrDefault; -+import io.papermc.paper.configuration.type.DoubleOrDefault; +import io.papermc.paper.configuration.type.Duration; +import io.papermc.paper.configuration.type.DurationOrDisabled; +import io.papermc.paper.configuration.type.EngineMode; -+import io.papermc.paper.configuration.type.IntOr; +import io.papermc.paper.configuration.type.fallback.ArrowDespawnRate; +import io.papermc.paper.configuration.type.fallback.AutosavePeriod; ++import io.papermc.paper.configuration.type.number.BelowZeroToEmpty; ++import io.papermc.paper.configuration.type.number.DoubleOr; ++import io.papermc.paper.configuration.type.number.IntOr; +import it.unimi.dsi.fastutil.objects.Reference2IntMap; +import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.Reference2LongMap; @@ -1550,6 +1397,7 @@ index 0000000000000000000000000000000000000000..456156e5f4adbe383a611bc81cebd044 +import org.spigotmc.SpigotWorldConfig; +import org.spigotmc.TrackingRange; +import org.spongepowered.configurate.objectmapping.ConfigSerializable; ++import org.spongepowered.configurate.objectmapping.meta.PostProcess; +import org.spongepowered.configurate.objectmapping.meta.Required; +import org.spongepowered.configurate.objectmapping.meta.Setting; + @@ -1695,11 +1543,12 @@ index 0000000000000000000000000000000000000000..456156e5f4adbe383a611bc81cebd044 + } + + public boolean allChunksAreSlimeChunks = false; -+ @Constraint(Constraints.BelowZeroDoubleToDefault.class) -+ public DoubleOrDefault skeletonHorseThunderSpawnChance = DoubleOrDefault.USE_DEFAULT; ++ @BelowZeroToEmpty ++ public DoubleOr.Default skeletonHorseThunderSpawnChance = DoubleOr.Default.USE_DEFAULT; + public boolean ironGolemsCanSpawnInAir = false; + public boolean countAllMobsForSpawning = false; -+ public int monsterSpawnMaxLightLevel = -1; ++ @BelowZeroToEmpty ++ public IntOr.Default monsterSpawnMaxLightLevel = IntOr.Default.USE_DEFAULT; + public DuplicateUUID duplicateUuid; + + public class DuplicateUUID extends ConfigurationPart { @@ -1770,7 +1619,8 @@ index 0000000000000000000000000000000000000000..456156e5f4adbe383a611bc81cebd044 + public int phantomsSpawnAttemptMinSeconds = 60; + public int phantomsSpawnAttemptMaxSeconds = 119; + public boolean parrotsAreUnaffectedByPlayerMovement = false; -+ public double zombieVillagerInfectionChance = -1.0; ++ @BelowZeroToEmpty ++ public DoubleOr.Default zombieVillagerInfectionChance = DoubleOr.Default.USE_DEFAULT; + public MobsCanAlwaysPickUpLoot mobsCanAlwaysPickUpLoot; + + public class MobsCanAlwaysPickUpLoot extends ConfigurationPart { @@ -1999,14 +1849,15 @@ index 0000000000000000000000000000000000000000..456156e5f4adbe383a611bc81cebd044 + @Setting(FeatureSeedsGeneration.FEATURE_SEEDS_KEY) + public FeatureSeeds featureSeeds; + -+ public class FeatureSeeds extends ConfigurationPart.Post { ++ public class FeatureSeeds extends ConfigurationPart { ++ @SuppressWarnings("unused") // Is used in FeatureSeedsGeneration + @Setting(FeatureSeedsGeneration.GENERATE_KEY) + public boolean generateRandomSeedsForAll = false; + @Setting(FeatureSeedsGeneration.FEATURES_KEY) + public Reference2LongMap>> features = new Reference2LongOpenHashMap<>(); + -+ @Override -+ public void postProcess() { ++ @PostProcess ++ private void postProcess() { + this.features.defaultReturnValue(-1); + } + } @@ -2028,7 +1879,6 @@ index 0000000000000000000000000000000000000000..456156e5f4adbe383a611bc81cebd044 + VANILLA, EIGENCRAFT, ALTERNATE_CURRENT + } + } -+ +} diff --git a/src/main/java/io/papermc/paper/configuration/constraint/Constraint.java b/src/main/java/io/papermc/paper/configuration/constraint/Constraint.java new file mode 100644 @@ -2068,45 +1918,26 @@ 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..6bcc08698be6eb1bf5607991d52f6dcf05db665c +index 0000000000000000000000000000000000000000..2d8c91007d5ebc051623bb308cf973bdad3f3273 --- /dev/null +++ b/src/main/java/io/papermc/paper/configuration/constraint/Constraints.java -@@ -0,0 +1,74 @@ +@@ -0,0 +1,43 @@ +package io.papermc.paper.configuration.constraint; + -+import com.mojang.logging.LogUtils; -+import io.papermc.paper.configuration.GlobalConfiguration; -+import io.papermc.paper.configuration.type.DoubleOrDefault; -+import org.checkerframework.checker.nullness.qual.Nullable; -+import org.slf4j.Logger; -+import org.spongepowered.configurate.objectmapping.meta.Constraint; -+import org.spongepowered.configurate.serialize.SerializationException; -+ +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Type; -+import java.util.OptionalDouble; ++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 Velocity implements Constraint { -+ -+ private static final Logger LOGGER = LogUtils.getClassLogger(); -+ -+ @Override -+ public void validate(final GlobalConfiguration.Proxies.@Nullable Velocity value) throws SerializationException { -+ if (value != null && value.enabled && value.secret.isEmpty()) { -+ LOGGER.error("Velocity is enabled, but no secret key was specified. A secret key is required. Disabling velocity..."); -+ value.enabled = false; -+ } -+ } -+ } -+ + public static final class Positive implements Constraint { + @Override + public void validate(@Nullable Number value) throws SerializationException { @@ -2116,18 +1947,6 @@ index 0000000000000000000000000000000000000000..6bcc08698be6eb1bf5607991d52f6dcf + } + } + -+ public static final class BelowZeroDoubleToDefault implements Constraint { -+ @Override -+ public void validate(final @Nullable DoubleOrDefault container) { -+ if (container != null) { -+ final OptionalDouble value = container.value(); -+ if (value.isPresent() && value.getAsDouble() < 0) { -+ container.value(OptionalDouble.empty()); -+ } -+ } -+ } -+ } -+ + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) @@ -2271,6 +2090,241 @@ index 0000000000000000000000000000000000000000..fe5cc1c097f8d8c135e6ead6f458426b + return node; + } +} +diff --git a/src/main/java/io/papermc/paper/configuration/mapping/InnerClassFieldDiscoverer.java b/src/main/java/io/papermc/paper/configuration/mapping/InnerClassFieldDiscoverer.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8f23276796037d048eb114952891a01a40971b3e +--- /dev/null ++++ b/src/main/java/io/papermc/paper/configuration/mapping/InnerClassFieldDiscoverer.java +@@ -0,0 +1,54 @@ ++package io.papermc.paper.configuration.mapping; ++ ++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; ++import org.spongepowered.configurate.serialize.SerializationException; ++ ++import static io.leangen.geantyref.GenericTypeReflector.erase; ++ ++public final class InnerClassFieldDiscoverer implements FieldDiscoverer> { ++ ++ private final InnerClassInstanceSupplier instanceSupplier; ++ private final FieldDiscoverer> delegate; ++ ++ @SuppressWarnings("unchecked") ++ public InnerClassFieldDiscoverer(final Map, Object> initialOverrides) { ++ this.instanceSupplier = new InnerClassInstanceSupplier(initialOverrides); ++ this.delegate = (FieldDiscoverer>) FieldDiscoverer.object(this.instanceSupplier); ++ } ++ ++ @Override ++ public @Nullable InstanceFactory> discover(final AnnotatedType target, final FieldCollector, V> collector) throws SerializationException { ++ final Class clazz = erase(target.getType()); ++ if (ConfigurationPart.class.isAssignableFrom(clazz)) { ++ final FieldDiscoverer.@Nullable InstanceFactory> instanceFactoryDelegate = this.delegate.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> mutableInstanceFactoryDelegate) { ++ return new InnerClassInstanceFactory(this.instanceSupplier, mutableInstanceFactoryDelegate, target); ++ } ++ } ++ return null; ++ } ++ ++ public static FieldDiscoverer worldConfig(WorldConfiguration worldConfiguration) { ++ final Map, Object> overrides = Map.of( ++ WorldConfiguration.class, worldConfiguration ++ ); ++ return new InnerClassFieldDiscoverer(overrides); ++ } ++ ++ public static FieldDiscoverer globalConfig() { ++ return new InnerClassFieldDiscoverer(Collections.emptyMap()); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/configuration/mapping/InnerClassInstanceFactory.java b/src/main/java/io/papermc/paper/configuration/mapping/InnerClassInstanceFactory.java +new file mode 100644 +index 0000000000000000000000000000000000000000..cec678ae24a7d99a46fa672be907f4c28fe4da96 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/configuration/mapping/InnerClassInstanceFactory.java +@@ -0,0 +1,65 @@ ++package io.papermc.paper.configuration.mapping; ++ ++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; ++import org.spongepowered.configurate.serialize.SerializationException; ++ ++import static io.leangen.geantyref.GenericTypeReflector.erase; ++ ++final class InnerClassInstanceFactory implements FieldDiscoverer.MutableInstanceFactory> { ++ ++ private final InnerClassInstanceSupplier instanceSupplier; ++ private final FieldDiscoverer.MutableInstanceFactory> fallback; ++ private final AnnotatedType targetType; ++ ++ InnerClassInstanceFactory(final InnerClassInstanceSupplier instanceSupplier, final FieldDiscoverer.MutableInstanceFactory> fallback, final AnnotatedType targetType) { ++ this.instanceSupplier = instanceSupplier; ++ this.fallback = fallback; ++ this.targetType = targetType; ++ } ++ ++ @Override ++ public Map begin() { ++ return this.fallback.begin(); ++ } ++ ++ @SuppressWarnings("unchecked") ++ @Override ++ public void complete(final Object instance, final Map intermediate) throws SerializationException { ++ final Iterator> iter = intermediate.entrySet().iterator(); ++ try { ++ while (iter.hasNext()) { // manually merge any mergeable maps ++ Map.Entry 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 existingMap = (Map) 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 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, SerializationException> { ++ ++ private final Map, Object> instanceMap = new HashMap<>(); ++ private final Map, Object> initialOverrides; ++ ++ /** ++ * @param initialOverrides map of types to objects to preload the config objects with. ++ */ ++ InnerClassInstanceSupplier(final Map, Object> initialOverrides) { ++ this.initialOverrides = initialOverrides; ++ } ++ ++ @Override ++ public Supplier 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 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, 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 ++ *

++ * 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 @@ -3839,13 +3893,13 @@ index 0000000000000000000000000000000000000000..edaa6ef28c1f9a223943969870889700 +} 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 -index 0000000000000000000000000000000000000000..f67e82fbeb97d68d5733bbd1c246183f5e91ab0a +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; + -+import io.papermc.paper.configuration.type.IntOr; ++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; @@ -3925,7 +3979,7 @@ index 0000000000000000000000000000000000000000..d08b65234192d5b639cead675114f64b +} diff --git a/src/main/java/io/papermc/paper/configuration/type/BooleanOrDefault.java b/src/main/java/io/papermc/paper/configuration/type/BooleanOrDefault.java new file mode 100644 -index 0000000000000000000000000000000000000000..33ae915b2462faf1705be3b195e113c154352653 +index 0000000000000000000000000000000000000000..5f03dcdff99bcd33bf789b0dd5521e39afbe09bf --- /dev/null +++ b/src/main/java/io/papermc/paper/configuration/type/BooleanOrDefault.java @@ -0,0 +1,53 @@ @@ -3968,7 +4022,7 @@ index 0000000000000000000000000000000000000000..33ae915b2462faf1705be3b195e113c1 + } else if (obj instanceof Boolean bool) { + return new BooleanOrDefault(bool); + } -+ throw new SerializationException(obj + "(" + type + ") is not a boolean or '" + DEFAULT_VALUE + "'"); ++ throw new SerializationException(BooleanOrDefault.class, obj + "(" + type + ") is not a boolean or '" + DEFAULT_VALUE + "'"); + } + + @Override @@ -3982,77 +4036,6 @@ index 0000000000000000000000000000000000000000..33ae915b2462faf1705be3b195e113c1 + } + } +} -diff --git a/src/main/java/io/papermc/paper/configuration/type/DoubleOrDefault.java b/src/main/java/io/papermc/paper/configuration/type/DoubleOrDefault.java -new file mode 100644 -index 0000000000000000000000000000000000000000..193709f1d08e489fc51cbe11d432529768ac1449 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/configuration/type/DoubleOrDefault.java -@@ -0,0 +1,65 @@ -+package io.papermc.paper.configuration.type; -+ -+import org.apache.commons.lang3.math.NumberUtils; -+import org.spongepowered.configurate.serialize.ScalarSerializer; -+import org.spongepowered.configurate.serialize.SerializationException; -+ -+import java.lang.reflect.Type; -+import java.util.OptionalDouble; -+import java.util.function.Predicate; -+ -+@SuppressWarnings("OptionalUsedAsFieldOrParameterType") -+public final class DoubleOrDefault { -+ private static final String DEFAULT_VALUE = "default"; -+ public static final DoubleOrDefault USE_DEFAULT = new DoubleOrDefault(OptionalDouble.empty()); -+ public static final ScalarSerializer SERIALIZER = new Serializer(); -+ -+ private OptionalDouble value; -+ -+ public DoubleOrDefault(final OptionalDouble value) { -+ this.value = value; -+ } -+ -+ public OptionalDouble value() { -+ return this.value; -+ } -+ -+ public void value(final OptionalDouble value) { -+ this.value = value; -+ } -+ -+ public double or(final double fallback) { -+ return this.value.orElse(fallback); -+ } -+ -+ private static final class Serializer extends ScalarSerializer { -+ Serializer() { -+ super(DoubleOrDefault.class); -+ } -+ -+ @Override -+ public DoubleOrDefault deserialize(final Type type, final Object obj) throws SerializationException { -+ if (obj instanceof String string) { -+ if (DEFAULT_VALUE.equalsIgnoreCase(string)) { -+ return USE_DEFAULT; -+ } -+ if (NumberUtils.isParsable(string)) { -+ return new DoubleOrDefault(OptionalDouble.of(Double.parseDouble(string))); -+ } -+ } else if (obj instanceof Number num) { -+ return new DoubleOrDefault(OptionalDouble.of(num.doubleValue())); -+ } -+ throw new SerializationException(obj + "(" + type + ") is not a double or '" + DEFAULT_VALUE + "'"); -+ } -+ -+ @Override -+ protected Object serialize(final DoubleOrDefault item, final Predicate> typeSupported) { -+ final OptionalDouble value = item.value(); -+ if (value.isPresent()) { -+ return value.getAsDouble(); -+ } else { -+ return DEFAULT_VALUE; -+ } -+ } -+ } -+} diff --git a/src/main/java/io/papermc/paper/configuration/type/Duration.java b/src/main/java/io/papermc/paper/configuration/type/Duration.java new file mode 100644 index 0000000000000000000000000000000000000000..422ccb0b332b3e94be228b9b94f379467d6461a5 @@ -4259,101 +4242,6 @@ index 0000000000000000000000000000000000000000..7f8b685762f59049fde88e8d1bc10e15 + return description; + } +} -diff --git a/src/main/java/io/papermc/paper/configuration/type/IntOr.java b/src/main/java/io/papermc/paper/configuration/type/IntOr.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4dc1e4a5d92f70b2eda44cbbac2747932892970d ---- /dev/null -+++ b/src/main/java/io/papermc/paper/configuration/type/IntOr.java -@@ -0,0 +1,89 @@ -+package io.papermc.paper.configuration.type; -+ -+import com.mojang.logging.LogUtils; -+import java.lang.reflect.Type; -+import java.util.OptionalInt; -+import java.util.function.Function; -+import java.util.function.IntPredicate; -+import java.util.function.Predicate; -+import org.apache.commons.lang3.math.NumberUtils; -+import org.slf4j.Logger; -+import org.spongepowered.configurate.serialize.ScalarSerializer; -+import org.spongepowered.configurate.serialize.SerializationException; -+ -+public interface IntOr { -+ -+ Logger LOGGER = LogUtils.getClassLogger(); -+ -+ default int or(final int fallback) { -+ return this.value().orElse(fallback); -+ } -+ -+ OptionalInt value(); -+ -+ 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 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 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 extends ScalarSerializer { -+ -+ private final Function creator; -+ private final String otherSerializedValue; -+ private final T otherValue; -+ -+ public Serializer(Class classOfT, Function creator, String otherSerializedValue, T otherValue) { -+ super(classOfT); -+ this.creator = creator; -+ this.otherSerializedValue = otherSerializedValue; -+ this.otherValue = otherValue; -+ } -+ -+ @Override -+ public T deserialize(Type type, Object obj) throws SerializationException { -+ if (obj instanceof String string) { -+ if (this.otherSerializedValue.equalsIgnoreCase(string)) { -+ return this.otherValue; -+ } -+ if (NumberUtils.isParsable(string)) { -+ return this.creator.apply(OptionalInt.of(Integer.parseInt(string))); -+ } -+ } else if (obj instanceof Number num) { -+ if (num.intValue() != num.doubleValue() || num.intValue() != num.longValue()) { -+ LOGGER.error("{} cannot be converted to an integer without losing information", num); -+ } -+ return this.creator.apply(OptionalInt.of(num.intValue())); -+ } -+ throw new SerializationException(obj + "(" + type + ") is not a integer or '" + this.otherSerializedValue + "'"); -+ } -+ -+ @Override -+ protected Object serialize(T item, Predicate> typeSupported) { -+ final OptionalInt value = item.value(); -+ if (value.isPresent()) { -+ return value.getAsInt(); -+ } else { -+ return this.otherSerializedValue; -+ } -+ } -+ } -+} 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 @@ -4628,6 +4516,237 @@ index 0000000000000000000000000000000000000000..70cc7b45e7355f6c8476a74a070f1266 + return value < 0 ? OptionalInt.empty() : OptionalInt.of(value); + } +} +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..5833c06b0707906ab7d10786ecd115f20e42e925 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/configuration/type/number/DoubleOr.java +@@ -0,0 +1,59 @@ ++package io.papermc.paper.configuration.type.number; ++ ++import com.google.common.base.Preconditions; ++import java.util.OptionalDouble; ++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 SERIALIZER = new Serializer<>(Default.class, Default::new, DEFAULT_VALUE, USE_DEFAULT); ++ } ++ ++ final class Serializer extends OptionalNumSerializer { ++ Serializer(final Class classOfT, final Function 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> 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..c43bc4d013b48ee367160be6514af1a574c2390c +--- /dev/null ++++ b/src/main/java/io/papermc/paper/configuration/type/number/IntOr.java +@@ -0,0 +1,79 @@ ++package io.papermc.paper.configuration.type.number; ++ ++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 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 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 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 extends OptionalNumSerializer { ++ ++ private Serializer(Class classOfT, Function 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) { ++ return false; ++ } ++ ++ @Override ++ protected Object serialize(final T item, final Predicate> 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 extends ScalarSerializer.Annotated { ++ ++ protected final String emptySerializedValue; ++ protected final T emptyValue; ++ private final Supplier empty; ++ private final Predicate isEmpty; ++ private final Function factory; ++ private final Class number; ++ ++ protected OptionalNumSerializer(final Class classOfT, final String emptySerializedValue, final T emptyValue, final Supplier empty, final Predicate isEmpty, final Function 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); ++} diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java index 2ac60ea28ce722cdef61cfdc69bbbdf391628238..9cfdd5d8c1650d9c9bdfbc07980239e507ff942d 100644 --- a/src/main/java/net/minecraft/server/Main.java diff --git a/patches/server/0013-Paper-Plugins.patch b/patches/server/0013-Paper-Plugins.patch index c12b8b945a..d61faaafdc 100644 --- a/patches/server/0013-Paper-Plugins.patch +++ b/patches/server/0013-Paper-Plugins.patch @@ -5147,7 +5147,7 @@ index 0000000000000000000000000000000000000000..7757d7df70e39a6fe4d92d02b6f905a2 +} diff --git a/src/main/java/io/papermc/paper/plugin/provider/configuration/serializer/PermissionConfigurationSerializer.java b/src/main/java/io/papermc/paper/plugin/provider/configuration/serializer/PermissionConfigurationSerializer.java new file mode 100644 -index 0000000000000000000000000000000000000000..d1088e4b7fa5f8e689f23b150b83645ce1ae5a0e +index 0000000000000000000000000000000000000000..f951f4024745503e9cdfa7ff17b9313ac6d7b4c4 --- /dev/null +++ b/src/main/java/io/papermc/paper/plugin/provider/configuration/serializer/PermissionConfigurationSerializer.java @@ -0,0 +1,56 @@ @@ -5192,7 +5192,7 @@ index 0000000000000000000000000000000000000000..d1088e4b7fa5f8e689f23b150b83645c + try { + result.add(Permission.loadPermission(entry.getKey().toString(), (Map) entry.getValue(), permissionDefault, result)); + } catch (Throwable ex) { -+ throw new SerializationException(null, "Error loading permission %s".formatted(entry.getKey()), ex); ++ throw new SerializationException((Type) null, "Error loading permission %s".formatted(entry.getKey()), ex); + } + } + } diff --git a/patches/server/0018-Rewrite-chunk-system.patch b/patches/server/0018-Rewrite-chunk-system.patch index 3970605ebd..edc653779b 100644 --- a/patches/server/0018-Rewrite-chunk-system.patch +++ b/patches/server/0018-Rewrite-chunk-system.patch @@ -15554,10 +15554,10 @@ index 0000000000000000000000000000000000000000..962d3cae6340fc11607b59355e291629 + +} diff --git a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -index dcfc085d355410723dbeef369579e1875a7a997b..8f82d5e1e263035eb23d878ffdc70c445933a207 100644 +index 73a34b4e378e6012a01c8ac8b092248298be6648..276961a11fc2bd747d2dacdc581cecec498d7593 100644 --- a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java +++ b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -@@ -25,6 +25,45 @@ public class GlobalConfiguration extends ConfigurationPart { +@@ -28,6 +28,45 @@ public class GlobalConfiguration extends ConfigurationPart { public static GlobalConfiguration get() { return instance; } @@ -15603,7 +15603,7 @@ index dcfc085d355410723dbeef369579e1875a7a997b..8f82d5e1e263035eb23d878ffdc70c44 static void set(GlobalConfiguration instance) { GlobalConfiguration.instance = instance; } -@@ -117,21 +156,6 @@ public class GlobalConfiguration extends ConfigurationPart { +@@ -129,21 +168,6 @@ public class GlobalConfiguration extends ConfigurationPart { public int incomingPacketThreshold = 300; } diff --git a/patches/server/0134-Use-TerminalConsoleAppender-for-console-improvements.patch b/patches/server/0134-Use-TerminalConsoleAppender-for-console-improvements.patch index 51e9914599..53aa870419 100644 --- a/patches/server/0134-Use-TerminalConsoleAppender-for-console-improvements.patch +++ b/patches/server/0134-Use-TerminalConsoleAppender-for-console-improvements.patch @@ -25,7 +25,7 @@ Other changes: Co-Authored-By: Emilia Kond diff --git a/build.gradle.kts b/build.gradle.kts -index 7c0b13c12f8f8ab43cc4128dc90101f527482a0d..619d1ff6a5305bf1567d244b1459a8ac5ffdbe3d 100644 +index ded8824a43c99754ea12544930b416c610bd2ead..f2285f5e5669ba5c13dc8a6eb61823b0bf213161 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,9 +6,30 @@ plugins { diff --git a/patches/server/0155-Handle-plugin-prefixes-using-Log4J-configuration.patch b/patches/server/0155-Handle-plugin-prefixes-using-Log4J-configuration.patch index a30cbbcc8b..e4c5341fe8 100644 --- a/patches/server/0155-Handle-plugin-prefixes-using-Log4J-configuration.patch +++ b/patches/server/0155-Handle-plugin-prefixes-using-Log4J-configuration.patch @@ -15,7 +15,7 @@ This may cause additional prefixes to be disabled for plugins bypassing the plugin logger. diff --git a/build.gradle.kts b/build.gradle.kts -index 619d1ff6a5305bf1567d244b1459a8ac5ffdbe3d..4f6b8060cb180b48ab9e3ec349b4f46a5a1b6363 100644 +index f2285f5e5669ba5c13dc8a6eb61823b0bf213161..3e07bc8be9b1406b8ecbb9a4b3c6ed05f48cdbde 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -25,7 +25,7 @@ dependencies { diff --git a/patches/server/0219-Use-AsyncAppender-to-keep-logging-IO-off-main-thread.patch b/patches/server/0219-Use-AsyncAppender-to-keep-logging-IO-off-main-thread.patch index e29f37abfd..070ea7f207 100644 --- a/patches/server/0219-Use-AsyncAppender-to-keep-logging-IO-off-main-thread.patch +++ b/patches/server/0219-Use-AsyncAppender-to-keep-logging-IO-off-main-thread.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Use AsyncAppender to keep logging IO off main thread diff --git a/build.gradle.kts b/build.gradle.kts -index 4f6b8060cb180b48ab9e3ec349b4f46a5a1b6363..d0f15081ed6ce6c633c46423fd666d74f50fdf8b 100644 +index 3e07bc8be9b1406b8ecbb9a4b3c6ed05f48cdbde..85c3b070da9dcde26b8f947e52ba390c535feae2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -38,6 +38,7 @@ dependencies { diff --git a/patches/server/0295-Implement-Brigadier-Mojang-API.patch b/patches/server/0295-Implement-Brigadier-Mojang-API.patch index f1d85062b9..ab7d00fd4a 100644 --- a/patches/server/0295-Implement-Brigadier-Mojang-API.patch +++ b/patches/server/0295-Implement-Brigadier-Mojang-API.patch @@ -10,7 +10,7 @@ Adds CommandRegisteredEvent - Allows manipulating the CommandNode to add more children/metadata for the client diff --git a/build.gradle.kts b/build.gradle.kts -index d0f15081ed6ce6c633c46423fd666d74f50fdf8b..ae99ecbf9055314764d32819dcdcb304caefb5ca 100644 +index 85c3b070da9dcde26b8f947e52ba390c535feae2..2f32a91bea8c27eb1f768e8a2a876a081cd68de8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -14,6 +14,7 @@ val alsoShade: Configuration by configurations.creating diff --git a/patches/server/0355-Configurable-chance-of-villager-zombie-infection.patch b/patches/server/0355-Configurable-chance-of-villager-zombie-infection.patch index 7b0fc6061a..fa42ff9be2 100644 --- a/patches/server/0355-Configurable-chance-of-villager-zombie-infection.patch +++ b/patches/server/0355-Configurable-chance-of-villager-zombie-infection.patch @@ -8,10 +8,10 @@ This allows you to solve an issue in vanilla behavior where: * On normal difficulty they will have a 50% of getting infected or dying. diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -index 2be074ecbc131b68574cf77ba9cab96424715522..55e4416a7988690ecfc8da6e83b43cbf9adda33b 100644 +index 2be074ecbc131b68574cf77ba9cab96424715522..4215c45aa1073b3c70eb58a06eb3dce26f389c90 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java +++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -@@ -452,12 +452,16 @@ public class Zombie extends Monster { +@@ -452,12 +452,8 @@ public class Zombie extends Monster { public boolean killedEntity(ServerLevel world, LivingEntity other) { boolean flag = super.killedEntity(world, other); @@ -19,16 +19,10 @@ index 2be074ecbc131b68574cf77ba9cab96424715522..55e4416a7988690ecfc8da6e83b43cbf - Villager entityvillager = (Villager) other; - - if (world.getDifficulty() != Difficulty.HARD && this.random.nextBoolean()) { -+ // Paper start -+ if (this.level().paperConfig().entities.behavior.zombieVillagerInfectionChance != 0.0 && (this.level().paperConfig().entities.behavior.zombieVillagerInfectionChance != -1.0 || world.getDifficulty() == Difficulty.NORMAL || world.getDifficulty() == Difficulty.HARD) && other instanceof Villager) { -+ if (this.level().paperConfig().entities.behavior.zombieVillagerInfectionChance == -1.0 && world.getDifficulty() != Difficulty.HARD && this.random.nextBoolean()) { - return flag; - } -+ if (this.level().paperConfig().entities.behavior.zombieVillagerInfectionChance != -1.0 && (this.random.nextDouble() * 100.0) > this.level().paperConfig().entities.behavior.zombieVillagerInfectionChance) { -+ return flag; -+ } // Paper end -+ -+ Villager entityvillager = (Villager) other; +- return flag; +- } ++ final double fallbackChance = world.getDifficulty() == Difficulty.HARD ? 1d : world.getDifficulty() == Difficulty.NORMAL ? 0.5d : 0d; // Paper ++ if (this.random.nextDouble() < world.paperConfig().entities.behavior.zombieVillagerInfectionChance.or(fallbackChance) && other instanceof Villager entityvillager) { // Paper // CraftBukkit start flag = Zombie.zombifyVillager(world, entityvillager, this.blockPosition(), this.isSilent(), CreatureSpawnEvent.SpawnReason.INFECTION) == null; } diff --git a/patches/server/0391-Deobfuscate-stacktraces-in-log-messages-crash-report.patch b/patches/server/0391-Deobfuscate-stacktraces-in-log-messages-crash-report.patch index e33c31ee32..cb39dc0d71 100644 --- a/patches/server/0391-Deobfuscate-stacktraces-in-log-messages-crash-report.patch +++ b/patches/server/0391-Deobfuscate-stacktraces-in-log-messages-crash-report.patch @@ -6,12 +6,12 @@ Subject: [PATCH] Deobfuscate stacktraces in log messages, crash reports, and diff --git a/build.gradle.kts b/build.gradle.kts -index ae99ecbf9055314764d32819dcdcb304caefb5ca..09e387db084b2bcff2b55d3eca23124e8d384283 100644 +index 2f32a91bea8c27eb1f768e8a2a876a081cd68de8..8d40814519e777182e95d8f48c02fa0e606a551f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -37,6 +37,7 @@ dependencies { testImplementation("org.mockito:mockito-core:4.9.0") // Paper - switch to mockito - implementation("org.spongepowered:configurate-yaml:4.1.2") // Paper - config files + implementation("org.spongepowered:configurate-yaml:4.2.0-SNAPSHOT") // Paper - config files implementation("commons-lang:commons-lang:2.6") + implementation("net.fabricmc:mapping-io:0.3.0") // Paper - needed to read mappings for stacktrace deobfuscation runtimeOnly("org.xerial:sqlite-jdbc:3.42.0.1") diff --git a/patches/server/0392-Implement-Mob-Goal-API.patch b/patches/server/0392-Implement-Mob-Goal-API.patch index 1f35b1bd05..01306798e0 100644 --- a/patches/server/0392-Implement-Mob-Goal-API.patch +++ b/patches/server/0392-Implement-Mob-Goal-API.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Implement Mob Goal API diff --git a/build.gradle.kts b/build.gradle.kts -index 09e387db084b2bcff2b55d3eca23124e8d384283..56b1eb0136777931b99524edc6077d138f41e023 100644 +index 8d40814519e777182e95d8f48c02fa0e606a551f..5a89e789d44fcf42838643c9023bc5ffc9b98606 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -46,6 +46,7 @@ dependencies { diff --git a/patches/server/0452-Add-zombie-targets-turtle-egg-config.patch b/patches/server/0452-Add-zombie-targets-turtle-egg-config.patch index e75c42314d..e14fe23350 100644 --- a/patches/server/0452-Add-zombie-targets-turtle-egg-config.patch +++ b/patches/server/0452-Add-zombie-targets-turtle-egg-config.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Add zombie targets turtle egg config diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -index 55e4416a7988690ecfc8da6e83b43cbf9adda33b..6b8315ab1bdd963c04e56f214f6bdfd6b5eb8f48 100644 +index 4215c45aa1073b3c70eb58a06eb3dce26f389c90..760b880a11e8ece98b41f934f9a2441fd020ecd6 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java +++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java @@ -109,7 +109,7 @@ public class Zombie extends Monster { diff --git a/patches/server/0523-Added-world-settings-for-mobs-picking-up-loot.patch b/patches/server/0523-Added-world-settings-for-mobs-picking-up-loot.patch index 19b35f04ac..54e0d6f0f9 100644 --- a/patches/server/0523-Added-world-settings-for-mobs-picking-up-loot.patch +++ b/patches/server/0523-Added-world-settings-for-mobs-picking-up-loot.patch @@ -18,10 +18,10 @@ index dadb419a04b343d6ba59353c6caa1a50aa07b67f..6be9e8d693bbb084791d7b30a1891ddb LocalDate localdate = LocalDate.now(); int i = localdate.get(ChronoField.DAY_OF_MONTH); diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -index 6b8315ab1bdd963c04e56f214f6bdfd6b5eb8f48..e28718fb76d6c53c31ed358779e28133de49cd7f 100644 +index 760b880a11e8ece98b41f934f9a2441fd020ecd6..99918974b1b763610425fc3d44d8453bd5d0725a 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java +++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -@@ -515,7 +515,7 @@ public class Zombie extends Monster { +@@ -507,7 +507,7 @@ public class Zombie extends Monster { Object object = super.finalizeSpawn(world, difficulty, spawnReason, entityData, entityNbt); float f = difficulty.getSpecialMultiplier(); diff --git a/patches/server/0527-Configurable-door-breaking-difficulty.patch b/patches/server/0527-Configurable-door-breaking-difficulty.patch index 4a11a39806..4aab6beaef 100644 --- a/patches/server/0527-Configurable-door-breaking-difficulty.patch +++ b/patches/server/0527-Configurable-door-breaking-difficulty.patch @@ -23,7 +23,7 @@ index 9fec5823d99d1ae8e41d9e21f7ddb8b8e94bf887..ad41bbbf5395ed18c2c9e1e692c7661a } diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -index e28718fb76d6c53c31ed358779e28133de49cd7f..b13365fe65507ad2935797a31baa8474b018f516 100644 +index 99918974b1b763610425fc3d44d8453bd5d0725a..87d909c2a34543ebdfc5d76aff7611d2d51144fb 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java +++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java @@ -100,7 +100,7 @@ public class Zombie extends Monster { diff --git a/patches/server/0693-Use-Velocity-compression-and-cipher-natives.patch b/patches/server/0693-Use-Velocity-compression-and-cipher-natives.patch index e184da95bc..9530dadb3f 100644 --- a/patches/server/0693-Use-Velocity-compression-and-cipher-natives.patch +++ b/patches/server/0693-Use-Velocity-compression-and-cipher-natives.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Use Velocity compression and cipher natives diff --git a/build.gradle.kts b/build.gradle.kts -index 56b1eb0136777931b99524edc6077d138f41e023..6528532d9e0f3d892fe81dc899f37d7cf611fecb 100644 +index 5a89e789d44fcf42838643c9023bc5ffc9b98606..c70fd0daeb312e339301c3a72e53a2e9b96759c3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -41,6 +41,11 @@ dependencies { diff --git a/patches/server/0738-Configurable-max-block-light-for-monster-spawning.patch b/patches/server/0738-Configurable-max-block-light-for-monster-spawning.patch index baf913d352..59eb60edd7 100644 --- a/patches/server/0738-Configurable-max-block-light-for-monster-spawning.patch +++ b/patches/server/0738-Configurable-max-block-light-for-monster-spawning.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Configurable max block light for monster spawning diff --git a/src/main/java/net/minecraft/world/entity/monster/Monster.java b/src/main/java/net/minecraft/world/entity/monster/Monster.java -index 6e0bd0eab0b06a4ac3042496bbb91292544e9f3c..55c245d0dfa369dc6de2197ae37335fba4fae4ae 100644 +index 6e0bd0eab0b06a4ac3042496bbb91292544e9f3c..e4218acaaf7d3aef0fb31f5597fb1af32aa2c8b5 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Monster.java +++ b/src/main/java/net/minecraft/world/entity/monster/Monster.java @@ -93,7 +93,7 @@ public abstract class Monster extends PathfinderMob implements Enemy { @@ -13,7 +13,7 @@ index 6e0bd0eab0b06a4ac3042496bbb91292544e9f3c..55c245d0dfa369dc6de2197ae37335fb } else { DimensionType dimensionType = world.dimensionType(); - int i = dimensionType.monsterSpawnBlockLightLimit(); -+ int i = world.getLevel().paperConfig().entities.spawning.monsterSpawnMaxLightLevel >= 0 ? world.getLevel().paperConfig().entities.spawning.monsterSpawnMaxLightLevel : dimensionType.monsterSpawnBlockLightLimit(); // Paper ++ int i = world.getLevel().paperConfig().entities.spawning.monsterSpawnMaxLightLevel.or(dimensionType.monsterSpawnBlockLightLimit()); // Paper if (i < 15 && world.getBrightness(LightLayer.BLOCK, pos) > i) { return false; } else { diff --git a/patches/server/0753-Fix-xp-reward-for-baby-zombies.patch b/patches/server/0753-Fix-xp-reward-for-baby-zombies.patch index 3d82857f71..dec1fe13a1 100644 --- a/patches/server/0753-Fix-xp-reward-for-baby-zombies.patch +++ b/patches/server/0753-Fix-xp-reward-for-baby-zombies.patch @@ -9,7 +9,7 @@ so this resets it after each call to Zombie#getExperienceReward diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -index b13365fe65507ad2935797a31baa8474b018f516..1ddbba72a5fd3d225b651815a38d178941fba289 100644 +index 87d909c2a34543ebdfc5d76aff7611d2d51144fb..3d44f8e9f8056d7d899e90e61240b2d1d109354b 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java +++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java @@ -175,11 +175,16 @@ public class Zombie extends Monster { diff --git a/patches/server/0811-Add-support-for-Proxy-Protocol.patch b/patches/server/0811-Add-support-for-Proxy-Protocol.patch index 5c2e95e96e..dca2d167d9 100644 --- a/patches/server/0811-Add-support-for-Proxy-Protocol.patch +++ b/patches/server/0811-Add-support-for-Proxy-Protocol.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Add support for Proxy Protocol diff --git a/build.gradle.kts b/build.gradle.kts -index 6528532d9e0f3d892fe81dc899f37d7cf611fecb..7afd8302e8407548c21e919f929125213635b21d 100644 +index c70fd0daeb312e339301c3a72e53a2e9b96759c3..c187641f0ec6444a10e0e1583e1697d07e8f0267 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -30,6 +30,7 @@ dependencies { diff --git a/patches/server/0854-Configurable-chat-thread-limit.patch b/patches/server/0854-Configurable-chat-thread-limit.patch index d79eb12950..5e312d3a14 100644 --- a/patches/server/0854-Configurable-chat-thread-limit.patch +++ b/patches/server/0854-Configurable-chat-thread-limit.patch @@ -22,26 +22,18 @@ is actually processed, this is honestly really just exposed for the misnomers or who just wanna ensure that this won't grow over a specific size if chat gets stupidly active diff --git a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -index 8f82d5e1e263035eb23d878ffdc70c445933a207..97a9ce438afc9094dca4a44cb25b37d5f88dcf43 100644 +index 276961a11fc2bd747d2dacdc581cecec498d7593..a6f58b3457b7477015c5c6d969e7d83017dd3fa1 100644 --- a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java +++ b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -@@ -285,13 +285,26 @@ public class GlobalConfiguration extends ConfigurationPart { - public Misc misc; +@@ -307,7 +307,18 @@ public class GlobalConfiguration extends ConfigurationPart { - public class Misc extends ConfigurationPart { -+ -+ public ChatThreads chatThreads; - public class ChatThreads extends ConfigurationPart.Post { - private int chatExecutorCoreSize = -1; - private int chatExecutorMaxSize = -1; - - @Override - public void postProcess() { -- // TODO: FILL + @PostProcess + private void postProcess() { +- // TODO: fill in separate patch + //noinspection ConstantConditions + if (net.minecraft.server.MinecraftServer.getServer() == null) return; // In testing env, this will be null here -+ int _chatExecutorMaxSize = (chatExecutorMaxSize <= 0) ? Integer.MAX_VALUE : chatExecutorMaxSize; // This is somewhat dumb, but, this is the default, do we cap this?; -+ int _chatExecutorCoreSize = Math.max(chatExecutorCoreSize, 0); ++ int _chatExecutorMaxSize = (this.chatExecutorMaxSize <= 0) ? Integer.MAX_VALUE : this.chatExecutorMaxSize; // This is somewhat dumb, but, this is the default, do we cap this?; ++ int _chatExecutorCoreSize = Math.max(this.chatExecutorCoreSize, 0); + + if (_chatExecutorMaxSize < _chatExecutorCoreSize) { + _chatExecutorMaxSize = _chatExecutorCoreSize;