From 4bfd5dbc061ff3d0be97cad704af2cbd79d426c7 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Sun, 20 Jun 2021 12:58:35 +0100
Subject: [PATCH] Fix Codec log spam

Mojang did NOT add dataconverters for world gen configurations
that they CHANGED. So, the codec fails to parse old data.

This fixes two instances:
- IntProvider is new and Mojang did not account for old data.
  Thankfully, only ColumnPlace needed to be special cased.
- TreeConfiguration had changes. Thankfully, they were
  only renames for one value and thankfully defaults could
  be provided for two new values (WITHOUT changing behavior).
---
 patches/server/Fix-Codec-log-spam.patch | 208 ++++++++++++++++++++++++
 1 file changed, 208 insertions(+)
 create mode 100644 patches/server/Fix-Codec-log-spam.patch

diff --git a/patches/server/Fix-Codec-log-spam.patch b/patches/server/Fix-Codec-log-spam.patch
new file mode 100644
index 0000000000..8976199b2e
--- /dev/null
+++ b/patches/server/Fix-Codec-log-spam.patch
@@ -0,0 +1,208 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Spottedleaf <Spottedleaf@users.noreply.github.com>
+Date: Sat, 19 Jun 2021 10:54:52 -0700
+Subject: [PATCH] Fix Codec log spam
+
+Mojang did NOT add dataconverters for world gen configurations
+that they CHANGED. So, the codec fails to parse old data.
+
+This fixes two instances:
+- IntProvider is new and Mojang did not account for old data.
+  Thankfully, only ColumnPlace needed to be special cased.
+- TreeConfiguration had changes. Thankfully, they were
+  only renames for one value and thankfully defaults could
+  be provided for two new values (WITHOUT changing behavior).
+
+diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/server/MCUtil.java
++++ b/src/main/java/net/minecraft/server/MCUtil.java
+@@ -0,0 +0,0 @@ public final class MCUtil {
+     public static int getTicketLevelFor(net.minecraft.world.level.chunk.ChunkStatus status) {
+         return net.minecraft.server.level.ChunkMap.MAX_VIEW_DISTANCE + net.minecraft.world.level.chunk.ChunkStatus.getDistance(status);
+     }
++
++    public static <A> com.mojang.serialization.MapCodec<A> fieldWithFallbacks(com.mojang.serialization.Codec<A> codec, String name, String ...fallback) {
++        return com.mojang.serialization.MapCodec.of(
++            new com.mojang.serialization.codecs.FieldEncoder<>(name, codec),
++            new FieldFallbackDecoder<>(name, java.util.Arrays.asList(fallback), codec),
++            () -> "FieldFallback[" + name + ": " + codec.toString() + "]"
++        );
++    }
++
++    // This is likely a common occurrence, sadly
++    public static final class FieldFallbackDecoder<A> extends com.mojang.serialization.MapDecoder.Implementation<A> {
++        protected final String name;
++        protected final List<String> fallback;
++        private final com.mojang.serialization.Decoder<A> elementCodec;
++
++        public FieldFallbackDecoder(final String name, final List<String> fallback, final com.mojang.serialization.Decoder<A> elementCodec) {
++            this.name = name;
++            this.fallback = fallback;
++            this.elementCodec = elementCodec;
++        }
++
++        @Override
++        public <T> com.mojang.serialization.DataResult<A> decode(final com.mojang.serialization.DynamicOps<T> ops, final com.mojang.serialization.MapLike<T> input) {
++            T value = input.get(name);
++            if (value == null) {
++                for (String fall : fallback) {
++                    value = input.get(fall);
++                    if (value != null) {
++                        break;
++                    }
++                }
++                if (value == null) {
++                    return com.mojang.serialization.DataResult.error("No key " + name + " in " + input);
++                }
++            }
++            return elementCodec.parse(ops, value);
++        }
++
++        @Override
++        public <T> java.util.stream.Stream<T> keys(final com.mojang.serialization.DynamicOps<T> ops) {
++            return java.util.stream.Stream.of(ops.createString(name));
++        }
++
++        @Override
++        public boolean equals(final Object o) {
++            if (this == o) {
++                return true;
++            }
++            if (o == null || getClass() != o.getClass()) {
++                return false;
++            }
++            final FieldFallbackDecoder<?> that = (FieldFallbackDecoder<?>)o;
++            return java.util.Objects.equals(name, that.name) && java.util.Objects.equals(elementCodec, that.elementCodec)
++                && java.util.Objects.equals(fallback, that.fallback);
++        }
++
++        @Override
++        public int hashCode() {
++            return java.util.Objects.hash(name, fallback, elementCodec);
++        }
++
++        @Override
++        public String toString() {
++            return "FieldDecoder[" + name + ": " + elementCodec + ']';
++        }
++    }
+ }
+diff --git a/src/main/java/net/minecraft/util/valueproviders/IntProvider.java b/src/main/java/net/minecraft/util/valueproviders/IntProvider.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/util/valueproviders/IntProvider.java
++++ b/src/main/java/net/minecraft/util/valueproviders/IntProvider.java
+@@ -0,0 +0,0 @@ import net.minecraft.core.Registry;
+ 
+ public abstract class IntProvider {
+     private static final Codec<Either<Integer, IntProvider>> CONSTANT_OR_DISPATCH_CODEC = Codec.either(Codec.INT, Registry.INT_PROVIDER_TYPES.dispatch(IntProvider::getType, IntProviderType::codec));
+-    public static final Codec<IntProvider> CODEC = CONSTANT_OR_DISPATCH_CODEC.xmap((either) -> {
++    public static final Codec<IntProvider> CODEC_REAL = CONSTANT_OR_DISPATCH_CODEC.xmap((either) -> { // Paper - used by CODEC below
+         return either.map(ConstantInt::of, (intProvider) -> {
+             return intProvider;
+         });
+     }, (intProvider) -> {
+         return intProvider.getType() == IntProviderType.CONSTANT ? Either.left(((ConstantInt)intProvider).getValue()) : Either.right(intProvider);
+     });
++    // Paper start
++    public static final Codec<IntProvider> CODEC = new Codec<>() {
++        @Override
++        public <T> DataResult<com.mojang.datafixers.util.Pair<IntProvider, T>> decode(com.mojang.serialization.DynamicOps<T> ops, T input) {
++            /*
++            UniformInt:
++            count -> { (old format)
++                base, spread
++            } -> {UniformInt} { (new format & type)
++                base, base + spread
++            } */
++
++
++            if (ops.get(input, "base").result().isPresent() && ops.get(input, "spread").result().isPresent()) {
++                // detected old format
++                int base = ops.getNumberValue(ops.get(input, "base").result().get()).result().get().intValue();
++                int spread = ops.getNumberValue(ops.get(input, "spread").result().get()).result().get().intValue();
++                return DataResult.success(new com.mojang.datafixers.util.Pair<>(UniformInt.of(base, base + spread), input));
++            }
++
++            // not old format, forward to real codec
++            return CODEC_REAL.decode(ops, input);
++        }
++
++        @Override
++        public <T> DataResult<T> encode(IntProvider input, com.mojang.serialization.DynamicOps<T> ops, T prefix) {
++            // forward to real codec
++            return CODEC_REAL.encode(input, ops, prefix);
++        }
++    };
++    // Paper end
+     public static final Codec<IntProvider> NON_NEGATIVE_CODEC = codec(0, Integer.MAX_VALUE);
+     public static final Codec<IntProvider> POSITIVE_CODEC = codec(1, Integer.MAX_VALUE);
+ 
+diff --git a/src/main/java/net/minecraft/world/level/levelgen/feature/blockplacers/ColumnPlacer.java b/src/main/java/net/minecraft/world/level/levelgen/feature/blockplacers/ColumnPlacer.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/world/level/levelgen/feature/blockplacers/ColumnPlacer.java
++++ b/src/main/java/net/minecraft/world/level/levelgen/feature/blockplacers/ColumnPlacer.java
+@@ -0,0 +0,0 @@ import net.minecraft.world.level.LevelAccessor;
+ import net.minecraft.world.level.block.state.BlockState;
+ 
+ public class ColumnPlacer extends BlockPlacer {
+-    public static final Codec<ColumnPlacer> CODEC = RecordCodecBuilder.create((instance) -> {
++    public static final Codec<ColumnPlacer> CODEC_REAL = RecordCodecBuilder.create((instance) -> { // Paper - used by CODEC below
+         return instance.group(IntProvider.NON_NEGATIVE_CODEC.fieldOf("size").forGetter((columnPlacer) -> {
+             return columnPlacer.size;
+         })).apply(instance, ColumnPlacer::new);
+     });
++    // Paper start
++    public static final Codec<ColumnPlacer> CODEC = new Codec<>() {
++        @Override
++        public <T> com.mojang.serialization.DataResult<com.mojang.datafixers.util.Pair<ColumnPlacer, T>> decode(com.mojang.serialization.DynamicOps<T> ops, T input) {
++            /*
++                Old format:
++                min_size, extra_size -> BiasedToBottomInt(min_size, min_size + extra_size) to place @ root.size
++             */
++            if (ops.get(input, "min_size").result().isPresent() && ops.get(input, "extra_size").result().isPresent()) {
++                // detected old format
++                int min_size = ops.getNumberValue(ops.get(input, "min_size").result().get()).result().get().intValue();
++                int extra_size = ops.getNumberValue(ops.get(input, "extra_size").result().get()).result().get().intValue();
++                return com.mojang.serialization.DataResult.success(
++                    new com.mojang.datafixers.util.Pair<>(new ColumnPlacer(net.minecraft.util.valueproviders.BiasedToBottomInt.of(
++                        min_size, min_size + extra_size
++                    )), input)
++                );
++            }
++
++            // not old format, forward to real codec
++            return CODEC_REAL.decode(ops, input);
++        }
++
++        @Override
++        public <T> com.mojang.serialization.DataResult<T> encode(ColumnPlacer input, com.mojang.serialization.DynamicOps<T> ops, T prefix) {
++            // forward to real codec
++            return CODEC_REAL.encode(input, ops, prefix);
++        }
++    };
++    // Paper end
+     private final IntProvider size;
+ 
+     public ColumnPlacer(IntProvider size) {
+diff --git a/src/main/java/net/minecraft/world/level/levelgen/feature/configurations/TreeConfiguration.java b/src/main/java/net/minecraft/world/level/levelgen/feature/configurations/TreeConfiguration.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/world/level/levelgen/feature/configurations/TreeConfiguration.java
++++ b/src/main/java/net/minecraft/world/level/levelgen/feature/configurations/TreeConfiguration.java
+@@ -0,0 +0,0 @@ public class TreeConfiguration implements FeatureConfiguration {
+             return treeConfiguration.trunkProvider;
+         }), TrunkPlacer.CODEC.fieldOf("trunk_placer").forGetter((treeConfiguration) -> {
+             return treeConfiguration.trunkPlacer;
+-        }), BlockStateProvider.CODEC.fieldOf("foliage_provider").forGetter((treeConfiguration) -> {
++        }), net.minecraft.server.MCUtil.fieldWithFallbacks(BlockStateProvider.CODEC, "foliage_provider", "leaves_provider").forGetter((treeConfiguration) -> { // Paper - provide fallback for rename
+             return treeConfiguration.foliageProvider;
+-        }), BlockStateProvider.CODEC.fieldOf("sapling_provider").forGetter((treeConfiguration) -> {
++        }), BlockStateProvider.CODEC.optionalFieldOf("sapling_provider", new SimpleStateProvider(Blocks.OAK_SAPLING.defaultBlockState())).forGetter((treeConfiguration) -> { // Paper - provide default - it looks like for now this is OK because it's just used to check canSurvive. Same check happens in 1.16.5 for the default we provide - so it should retain behavior...
+             return treeConfiguration.saplingProvider;
+         }), FoliagePlacer.CODEC.fieldOf("foliage_placer").forGetter((treeConfiguration) -> {
+             return treeConfiguration.foliagePlacer;
+-        }), BlockStateProvider.CODEC.fieldOf("dirt_provider").forGetter((treeConfiguration) -> {
++        }), BlockStateProvider.CODEC.optionalFieldOf("dirt_provider", new SimpleStateProvider(Blocks.DIRT.defaultBlockState())).forGetter((treeConfiguration) -> { // Paper - provide defaults, old data DOES NOT have this key (thankfully ALL OLD DATA used DIRT)
+             return treeConfiguration.dirtProvider;
+         }), FeatureSize.CODEC.fieldOf("minimum_size").forGetter((treeConfiguration) -> {
+             return treeConfiguration.minimumSize;