From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Spottedleaf 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 86d1bb22665bf46c7744ef653eda0caefc2a4337..a97295b420b55834687b76cfee1dc98c6d6acfd6 100644 --- a/src/main/java/net/minecraft/server/MCUtil.java +++ b/src/main/java/net/minecraft/server/MCUtil.java @@ -718,4 +718,70 @@ 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 com.mojang.serialization.MapCodec fieldWithFallbacks(com.mojang.serialization.Codec 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 extends com.mojang.serialization.MapDecoder.Implementation { + protected final String name; + protected final List fallback; + private final com.mojang.serialization.Decoder elementCodec; + + public FieldFallbackDecoder(final String name, final List fallback, final com.mojang.serialization.Decoder elementCodec) { + this.name = name; + this.fallback = fallback; + this.elementCodec = elementCodec; + } + + @Override + public com.mojang.serialization.DataResult decode(final com.mojang.serialization.DynamicOps ops, final com.mojang.serialization.MapLike 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 java.util.stream.Stream keys(final com.mojang.serialization.DynamicOps 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 020a19cd683dd3779c5116d12b3cdcd3b3ca69b4..c81a0eec12436c10869bfdcc21af09173f438331 100644 --- a/src/main/java/net/minecraft/util/valueproviders/IntProvider.java +++ b/src/main/java/net/minecraft/util/valueproviders/IntProvider.java @@ -9,13 +9,44 @@ import net.minecraft.core.Registry; public abstract class IntProvider { private static final Codec> CONSTANT_OR_DISPATCH_CODEC = Codec.either(Codec.INT, Registry.INT_PROVIDER_TYPES.dispatch(IntProvider::getType, IntProviderType::codec)); - public static final Codec CODEC = CONSTANT_OR_DISPATCH_CODEC.xmap((either) -> { + public static final Codec 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 CODEC = new Codec<>() { + @Override + public DataResult> decode(com.mojang.serialization.DynamicOps 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 DataResult encode(IntProvider input, com.mojang.serialization.DynamicOps ops, T prefix) { + // forward to real codec + return CODEC_REAL.encode(input, ops, prefix); + } + }; + // Paper end public static final Codec NON_NEGATIVE_CODEC = codec(0, Integer.MAX_VALUE); public static final Codec 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 05bba5410fbd9f8e333584ccbd65a909f3040322..82859e1b048ba8a96cc67e085e9ed01b6bd3a6cd 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 @@ -10,11 +10,28 @@ import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.block.state.BlockState; public class ColumnPlacer extends BlockPlacer { + // Paper start public static final Codec CODEC = RecordCodecBuilder.create((instance) -> { - return instance.group(IntProvider.NON_NEGATIVE_CODEC.fieldOf("size").forGetter((columnPlacer) -> { - return columnPlacer.size; - })).apply(instance, ColumnPlacer::new); + return instance.group( + IntProvider.NON_NEGATIVE_CODEC.optionalFieldOf("size").forGetter((columnPlacer) -> { + return java.util.Optional.of(columnPlacer.size); + }), + Codec.INT.optionalFieldOf("min_size").forGetter((columnPlacer) -> { + return java.util.Optional.empty(); + }), + Codec.INT.optionalFieldOf("extra_size").forGetter((columnPlacer) -> { + return java.util.Optional.empty(); + }) + ).apply(instance, ColumnPlacer::new); }); + public ColumnPlacer(java.util.Optional size, java.util.Optional minSize, java.util.Optional extraSize) { + if (size.isPresent()) { + this.size = size.get(); + } else { + this.size = net.minecraft.util.valueproviders.BiasedToBottomInt.of(minSize.get().intValue(), minSize.get().intValue() + extraSize.get().intValue()); + } + } + // 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 5da68897148192905c2747676c1ee2ee649f923f..b990099cf274f8cb0d96c139345cf0bf328affd6 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 @@ -18,13 +18,13 @@ 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;