diff --git a/src/client/java/io/github/startsmercury/visual_snowy_leaves/impl/client/VisualSnowyLeavesImpl.java b/src/client/java/io/github/startsmercury/visual_snowy_leaves/impl/client/VisualSnowyLeavesImpl.java index c0d5397..bb754e4 100644 --- a/src/client/java/io/github/startsmercury/visual_snowy_leaves/impl/client/VisualSnowyLeavesImpl.java +++ b/src/client/java/io/github/startsmercury/visual_snowy_leaves/impl/client/VisualSnowyLeavesImpl.java @@ -192,9 +192,9 @@ private boolean loadConfig() { return false; } - Config.CODEC + Config.LENIENT_CODEC .decode(JsonOps.INSTANCE, json) - .ifSuccess(result -> this.setConfig(result.getFirst())) + .ifSuccess(result -> this.setConfig(result.getFirst().upgrade())) .ifError(result -> this.logger .warn("[{}] Unable to decode config: {}", VslConstants.NAME, result.message()) ); diff --git a/src/client/java/io/github/startsmercury/visual_snowy_leaves/impl/client/config/Config.java b/src/client/java/io/github/startsmercury/visual_snowy_leaves/impl/client/config/Config.java index 129d1db..8aba670 100644 --- a/src/client/java/io/github/startsmercury/visual_snowy_leaves/impl/client/config/Config.java +++ b/src/client/java/io/github/startsmercury/visual_snowy_leaves/impl/client/config/Config.java @@ -2,16 +2,20 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.DataResult; +import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; import net.minecraft.resources.ResourceLocation; import java.util.ArrayList; import java.util.Comparator; +import java.util.HashSet; import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; +import java.util.function.Function; + +import static io.github.startsmercury.visual_snowy_leaves.impl.client.config.Leaves.*; public record Config( + int version, RebuildInterval rebuildInterval, SnowyMode snowyMode, Set targetBlockKeys, @@ -33,33 +37,123 @@ public record Config( } ); - public static final Codec CODEC = RecordCodecBuilder.create(instance -> - instance.group( - RebuildInterval.CODEC.fieldOf("rebuildInterval").forGetter(Config::rebuildInterval), - SnowyMode.CODEC.fieldOf("snowyMode").forGetter(Config::snowyMode), - TARGET_BLOCK_KEYS_CODEC.fieldOf("targetBlockKeys").forGetter(Config::targetBlockKeys), - TransitionDuration.CODEC.fieldOf("transitionDuration").forGetter(Config::transitionDuration) - ).apply(instance, Config::new)); + public static final int MINIMUM_VERSION = 0; + + public static final int CURRENT_VERSION = 1; public static final RebuildInterval DEFAULT_REBUILD_INTERVAL = RebuildInterval.fromTicks(20); public static final SnowyMode DEFAULT_SNOWY_MODE = SnowyMode.SNOWING; - public static final Set DEFAULT_TARGET_BLOCK_KEYS = Stream.of( - "oak", "spruce", "birch", "jungle", "acacia", "dark_oak", "mangrove" - ) - .map(base -> base + "_leaves") - .map(ResourceLocation::parse) - .collect(Collectors.toUnmodifiableSet()); + public static final Set DEFAULT_TARGET_BLOCK_KEYS = Set.of( + OAK, SPRUCE, BIRCH, JUNGLE, ACACIA, DARK_OAK, MANGROVE, PALE_OAK + ); @SuppressWarnings("deprecation") public static final TransitionDuration DEFAULT_TRANSITION_DURATION = TransitionDuration.fromTicksUnchecked(400); + @FunctionalInterface + private interface CodecFieldBuilder { + CodecFieldBuilder DEFAULT = CodecFieldBuilder::fieldOfImpl; + CodecFieldBuilder OPTIONAL = Codec::optionalFieldOf; + + private static MapCodec fieldOfImpl( + final Codec self, + final String name, + final A defaultValue + ) { + return self.fieldOf(name); + } + + MapCodec create(Codec self, String name, A defaultValue); + + default RecordCodecBuilder create( + final Codec self, + final String name, + final A defaultValue, + final Function getter + ) { + return create(self, name, defaultValue).forGetter(getter); + } + } + + private static Codec newCodec(final CodecFieldBuilder fieldBuilder) { + return RecordCodecBuilder.create(instance -> + instance.group( + fieldBuilder.create( + Codec.INT, + // Defaults may be partial, assume minimum version + "version", + MINIMUM_VERSION, + Config::version + ), + fieldBuilder.create( + RebuildInterval.CODEC, + "rebuildInterval", + DEFAULT_REBUILD_INTERVAL, + Config::rebuildInterval + ), + fieldBuilder.create( + SnowyMode.CODEC, + "snowyMode", + DEFAULT_SNOWY_MODE, + Config::snowyMode + ), + fieldBuilder.create( + TARGET_BLOCK_KEYS_CODEC, + "targetBlockKeys", + DEFAULT_TARGET_BLOCK_KEYS, + Config::targetBlockKeys + ), + fieldBuilder.create( + TransitionDuration.CODEC, + "transitionDuration", + DEFAULT_TRANSITION_DURATION, + Config::transitionDuration + ) + ).apply(instance, Config::new)); + } + + public static final Codec LENIENT_CODEC = newCodec(CodecFieldBuilder.OPTIONAL); + + public static final Codec CODEC = newCodec(CodecFieldBuilder.DEFAULT); + public static final Config DEFAULT = new Config( + CURRENT_VERSION, DEFAULT_REBUILD_INTERVAL, DEFAULT_SNOWY_MODE, DEFAULT_TARGET_BLOCK_KEYS, DEFAULT_TRANSITION_DURATION ); + + public Config upgrade() { + if (version >= CURRENT_VERSION) { + return this; + } + + final var version = Math.max(this.version, MINIMUM_VERSION); + var rebuildInterval = this.rebuildInterval; + var snowyMode = this.snowyMode; + var targetBlockKeys = new HashSet<>(this.targetBlockKeys); + var transitionDuration = this.transitionDuration; + + switch (version) { + case 0 -> targetBlockKeys.add(PALE_OAK); + default -> { + final var message = "Upgrade is not yet implemented for config version " + + version + + ". Immediately report this issue"; + throw new InternalError(message); + } + } + + return new Config( + CURRENT_VERSION, + rebuildInterval, + snowyMode, + Set.copyOf(targetBlockKeys), + transitionDuration + ); + } } diff --git a/src/client/java/io/github/startsmercury/visual_snowy_leaves/impl/client/config/Leaves.java b/src/client/java/io/github/startsmercury/visual_snowy_leaves/impl/client/config/Leaves.java new file mode 100644 index 0000000..9426b6e --- /dev/null +++ b/src/client/java/io/github/startsmercury/visual_snowy_leaves/impl/client/config/Leaves.java @@ -0,0 +1,23 @@ +package io.github.startsmercury.visual_snowy_leaves.impl.client.config; + +import net.minecraft.resources.ResourceLocation; + +final class Leaves { + private static ResourceLocation getLeaves(final String baseName) { + return ResourceLocation.parse(baseName + "_leaves"); + } + + // Version 0 + public static final ResourceLocation OAK = getLeaves("oak"); + public static final ResourceLocation SPRUCE = getLeaves("spruce"); + public static final ResourceLocation BIRCH = getLeaves("birch"); + public static final ResourceLocation ACACIA = getLeaves("acacia"); + public static final ResourceLocation JUNGLE = getLeaves("jungle"); + public static final ResourceLocation DARK_OAK = getLeaves("dark_oak"); + public static final ResourceLocation MANGROVE = getLeaves("mangrove"); + + // Version 1 + public static final ResourceLocation PALE_OAK = getLeaves("pale_oak"); + + private Leaves() {} +} diff --git a/src/client/java/io/github/startsmercury/visual_snowy_leaves/impl/client/config/TransitionDuration.java b/src/client/java/io/github/startsmercury/visual_snowy_leaves/impl/client/config/TransitionDuration.java index 9b0d765..f55de37 100644 --- a/src/client/java/io/github/startsmercury/visual_snowy_leaves/impl/client/config/TransitionDuration.java +++ b/src/client/java/io/github/startsmercury/visual_snowy_leaves/impl/client/config/TransitionDuration.java @@ -6,7 +6,6 @@ import net.minecraft.SharedConstants; import java.time.Duration; -import java.util.Objects; import static io.github.startsmercury.visual_snowy_leaves.impl.client.VslConstants.Duration.ONE_TICK;