From 4ded614f0b1b2dc72e5f059584fc9758812b5899 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Wed, 24 Apr 2024 23:49:13 -0700
Subject: [PATCH] dataconverter

---
 .../server/Rewrite-dataconverter-system.patch | 6039 ++++++++++++++---
 1 file changed, 4999 insertions(+), 1040 deletions(-)
 rename patches/{unapplied => }/server/Rewrite-dataconverter-system.patch (89%)

diff --git a/patches/unapplied/server/Rewrite-dataconverter-system.patch b/patches/server/Rewrite-dataconverter-system.patch
similarity index 89%
rename from patches/unapplied/server/Rewrite-dataconverter-system.patch
rename to patches/server/Rewrite-dataconverter-system.patch
index 79898568a4..59be9b9dc3 100644
--- a/patches/unapplied/server/Rewrite-dataconverter-system.patch
+++ b/patches/server/Rewrite-dataconverter-system.patch
@@ -104,9 +104,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +import ca.spottedleaf.dataconverter.types.MapType;
 +
-+public interface DataWalker<K> {
++public interface DataWalker<T> {
 +
-+    public MapType<String> walk(final MapType<K> data, final long fromVersion, final long toVersion);
++    public T walk(final T data, final long fromVersion, final long toVersion);
 +
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCDataConverter.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCDataConverter.java
@@ -120,6 +120,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
 +import ca.spottedleaf.dataconverter.converters.datatypes.DataType;
 +import ca.spottedleaf.dataconverter.minecraft.datatypes.MCDataType;
++import ca.spottedleaf.dataconverter.minecraft.versions.V99;
 +import ca.spottedleaf.dataconverter.types.json.JsonMapType;
 +import ca.spottedleaf.dataconverter.types.nbt.NBTMapType;
 +import com.google.gson.JsonObject;
@@ -159,7 +160,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    public static <T, R> R convert(final DataType<T, R> type, final T data, int fromVersion, final int toVersion) {
 +        Object ret = data;
 +
-+        long currentVersion = DataConverter.encodeVersions(fromVersion < 99 ? 99 : fromVersion, Integer.MAX_VALUE);
++        long currentVersion = DataConverter.encodeVersions(fromVersion < V99.VERSION ? V99.VERSION : fromVersion, Integer.MAX_VALUE);
 +        final long nextVersion = DataConverter.encodeVersions(toVersion, Integer.MAX_VALUE);
 +
 +        for (int i = 0, len = BREAKPOINTS.size(); i < len; ++i) {
@@ -192,7 +193,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    private MCDataConverter() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCVersionRegistry.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCVersionRegistry.java
 new file mode 100644
@@ -220,15 +220,15 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +    private static final Logger LOGGER = LogUtils.getLogger();
 +
-+    protected static final Int2ObjectLinkedOpenHashMap<String> VERSION_NAMES = new Int2ObjectLinkedOpenHashMap<>();
-+    protected static final IntArrayList VERSION_LIST;
-+    protected static final LongArrayList DATA_VERSION_LIST;
++    private static final Int2ObjectLinkedOpenHashMap<String> VERSION_NAMES = new Int2ObjectLinkedOpenHashMap<>();
++    private static final IntArrayList VERSION_LIST;
++    private static final LongArrayList DATA_VERSION_LIST;
 +
-+    protected static final IntArrayList DATACONVERTER_VERSIONS_LIST;
-+    protected static final IntLinkedOpenHashSet DATACONVERTER_VERSIONS_MAJOR = new IntLinkedOpenHashSet();
-+    protected static final LongLinkedOpenHashSet DATACONVERTER_VERSIONS = new LongLinkedOpenHashSet();
-+    protected static final Int2ObjectLinkedOpenHashMap<IntArrayList> SUBVERSIONS = new Int2ObjectLinkedOpenHashMap<>();
-+    protected static final LongArrayList BREAKPOINTS = new LongArrayList();
++    private static final IntArrayList DATACONVERTER_VERSIONS_LIST;
++    private static final IntLinkedOpenHashSet DATACONVERTER_VERSIONS_MAJOR = new IntLinkedOpenHashSet();
++    private static final LongLinkedOpenHashSet DATACONVERTER_VERSIONS = new LongLinkedOpenHashSet();
++    private static final Int2ObjectLinkedOpenHashMap<IntArrayList> SUBVERSIONS = new Int2ObjectLinkedOpenHashMap<>();
++    private static final LongArrayList BREAKPOINTS = new LongArrayList();
 +    static {
 +        // Note: Some of these are nameless.
 +        // Unless a data version is specified here, it will NOT have converters ran for it. Please add them on update!
@@ -426,7 +426,20 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                3683,
 +                3685,
 +                3692,
-+                // All up to 1.20.3
++                3800,
++                3803,
++                3807,
++                3808,
++                3809,
++                3812,
++                3813,
++                3814,
++                3818,
++                3820,
++                3825,
++                3828,
++                3833
++                // All up to 1.20.5
 +        };
 +        Arrays.sort(converterVersions);
 +
@@ -443,22 +456,35 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        registerSubVersion(MCVersions.V17W47A, 6);
 +        registerSubVersion(MCVersions.V17W47A, 7);
 +
++        registerSubVersion(MCVersions.V24W04A + 1, 1);
++        registerSubVersion(MCVersions.V24W04A + 2, 1);
++
++        registerSubVersion(MCVersions.V24W07A + 1, 1);
++        registerSubVersion(MCVersions.V24W07A + 1, 2);
++        registerSubVersion(MCVersions.V24W07A + 1, 4);
++        registerSubVersion(MCVersions.V24W07A + 1, 5);
++        registerSubVersion(MCVersions.V24W07A + 1, 6);
++
 +        // register breakpoints here
 +        // for all major releases after 1.16, add them. this reduces the work required to determine if a breakpoint
 +        // is needed for new converters
 +
 +        // Too much changed in this version.
 +        registerBreakpoint(MCVersions.V17W47A);
-+        registerBreakpoint(MCVersions.V17W47A, Integer.MAX_VALUE);
++        registerBreakpointAfter(MCVersions.V17W47A, Integer.MAX_VALUE);
 +
 +        // final release of major version
-+        registerBreakpoint(MCVersions.V1_17_1, Integer.MAX_VALUE);
++        registerBreakpointAfter(MCVersions.V1_17_1, Integer.MAX_VALUE);
 +
 +        // final release of major version
-+        registerBreakpoint(MCVersions.V1_18_2, Integer.MAX_VALUE);
++        registerBreakpointAfter(MCVersions.V1_18_2, Integer.MAX_VALUE);
 +
 +        // final release of major version
-+        registerBreakpoint(MCVersions.V1_19_4, Integer.MAX_VALUE);
++        registerBreakpointAfter(MCVersions.V1_19_4, Integer.MAX_VALUE);
++
++        // Too much changed in this version.
++        registerBreakpoint(MCVersions.V24W07A + 1, 5);
++        registerBreakpointAfter(MCVersions.V24W07A + 1, Integer.MAX_VALUE);
 +    }
 +
 +    static {
@@ -553,6 +579,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        BREAKPOINTS.add(DataConverter.encodeVersions(version, step));
 +    }
 +
++    private static void registerBreakpointAfter(final int version) {
++        registerBreakpointAfter(version, 0);
++    }
++
++    private static void registerBreakpointAfter(final int version, final int step) {
++        BREAKPOINTS.add(DataConverter.encodeVersions(version, step) + 1L);
++    }
++
 +    // returns only versions that have dataconverters
 +    public static boolean hasDataConverters(final int version) {
 +        return DATACONVERTER_VERSIONS_MAJOR.contains(version);
@@ -587,6 +621,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            throw new IllegalStateException("Version " + DataConverter.encodedToString(version) + " is not registered to have dataconverters, yet has a dataconverter");
 +        }
 +    }
++
++    private MCVersionRegistry() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCVersions.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCVersions.java
 new file mode 100644
@@ -1091,7 +1127,33 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    public static final int V1_20_3_PRE4          = 3696;
 +    public static final int V1_20_3_RC1           = 3697;
 +    public static final int V1_20_3               = 3698;
++    public static final int V1_20_4_RC1           = 3699;
++    public static final int V1_20_4               = 3700;
++    public static final int V23W51A               = 3801;
++    public static final int V23W51B               = 3802;
++    public static final int V24W03A               = 3804;
++    public static final int V24W03B               = 3805;
++    public static final int V24W04A               = 3806;
++    public static final int V24W05A               = 3809;
++    public static final int V24W05B               = 3811;
++    public static final int V24W06A               = 3815;
++    public static final int V24W07A               = 3817;
++    public static final int V24W09A               = 3819;
++    public static final int V24W10A               = 3821;
++    public static final int V24W11A               = 3823;
++    public static final int V24W12A               = 3824;
++    public static final int V24W13A               = 3826;
++    public static final int V24W14A               = 3827;
++    public static final int V1_20_5_PRE1          = 3829;
++    public static final int V1_20_5_PRE2          = 3830;
++    public static final int V1_20_5_PRE3          = 3831;
++    public static final int V1_20_5_PRE4          = 3832;
++    public static final int V1_20_5_RC1           = 3834;
++    public static final int V1_20_5_RC2           = 3835;
++    public static final int V1_20_5_RC3           = 3836;
++    public static final int V1_20_5               = 3837;
 +
++    private MCVersions() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterAbstractAdvancementsRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterAbstractAdvancementsRename.java
 new file mode 100644
@@ -1175,6 +1237,69 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +}
+diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/attributes/ConverterAbstractAttributesRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/attributes/ConverterAbstractAttributesRename.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/attributes/ConverterAbstractAttributesRename.java
+@@ -0,0 +0,0 @@
++package ca.spottedleaf.dataconverter.minecraft.converters.attributes;
++
++import ca.spottedleaf.dataconverter.converters.DataConverter;
++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper;
++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
++import ca.spottedleaf.dataconverter.types.ListType;
++import ca.spottedleaf.dataconverter.types.MapType;
++import ca.spottedleaf.dataconverter.types.ObjectType;
++import java.util.function.Function;
++
++public final class ConverterAbstractAttributesRename {
++
++    public static void register(final int version, final Function<String, String> renamer) {
++        register(version, 0, renamer);
++    }
++
++    public static void register(final int version, final int versionStep, final Function<String, String> renamer) {
++        final DataConverter<MapType<String>, MapType<String>> entityConverter = new DataConverter<>(version, versionStep) {
++            @Override
++            public MapType<String> convert(final MapType<String> data, final long sourceVersion, final long toVersion) {
++                final ListType attributes = data.getList("Attributes", ObjectType.MAP);
++
++                if (attributes == null) {
++                    return null;
++                }
++
++                for (int i = 0, len = attributes.size(); i < len; ++i) {
++                    RenameHelper.renameString(attributes.getMap(i), "Name", renamer);
++                }
++
++                return null;
++            }
++        };
++
++        MCTypeRegistry.ENTITY.addStructureConverter(entityConverter);
++        MCTypeRegistry.PLAYER.addStructureConverter(entityConverter);
++
++        MCTypeRegistry.ITEM_STACK.addStructureConverter(new DataConverter<>(version, versionStep) {
++            @Override
++            public MapType<String> convert(final MapType<String> data, final long sourceVersion, final long toVersion) {
++                final ListType attributes = data.getList("AttributeModifiers", ObjectType.MAP);
++
++                if (attributes == null) {
++                    return null;
++                }
++
++                for (int i = 0, len = attributes.size(); i < len; ++i) {
++                    RenameHelper.renameString(attributes.getMap(i), "AttributeName", renamer);
++                }
++
++                return null;
++            }
++        });
++    }
++
++    private ConverterAbstractAttributesRename() {}
++}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/blockname/ConverterAbstractBlockRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/blockname/ConverterAbstractBlockRename.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
@@ -1212,44 +1337,35 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                return null;
 +            }
 +        });
-+    }
-+
-+    public static void registerAndFixJigsaw(final int version, final Function<String, String> renamer) {
-+        registerAndFixJigsaw(version, 0, renamer);
-+    }
-+
-+    public static void registerAndFixJigsaw(final int version, final int subVersion, final Function<String, String> renamer) {
-+        register(version, subVersion, renamer);
-+        // TODO check on update, minecraft:jigsaw can change
-+        MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:jigsaw", new DataConverter<>(version, subVersion) {
++        MCTypeRegistry.FLAT_BLOCK_STATE.addConverter(new DataConverter<>(version, subVersion) {
 +            @Override
-+            public MapType<String> convert(final MapType<String> data, final long sourceVersion, final long toVersion) {
-+                final String finalState = data.getString("final_state");
-+                if (finalState == null || finalState.isEmpty()) {
++            public Object convert(final Object data, final long sourceVersion, final long toVersion) {
++                if (!(data instanceof String string)) {
 +                    return null;
 +                }
 +
-+                final int nbtStart1 = finalState.indexOf('[');
-+                final int nbtStart2 = finalState.indexOf('{');
-+                int stateNameEnd = finalState.length();
++                if (string.isEmpty()) {
++                    return null;
++                }
++
++                final int nbtStart1 = string.indexOf('[');
++                final int nbtStart2 = string.indexOf('{');
++                int stateNameEnd = string.length();
 +                if (nbtStart1 > 0) {
-+                    stateNameEnd = Math.min(stateNameEnd, nbtStart1);
++                    stateNameEnd = nbtStart1;
 +                }
 +
 +                if (nbtStart2 > 0) {
 +                    stateNameEnd = Math.min(stateNameEnd, nbtStart2);
 +                }
 +
-+                final String blockStateName = finalState.substring(0, stateNameEnd);
++                final String blockStateName = string.substring(0, stateNameEnd);
 +                final String converted = renamer.apply(blockStateName);
 +                if (converted == null) {
 +                    return null;
 +                }
 +
-+                final String convertedState = converted.concat(finalState.substring(stateNameEnd));
-+                data.setString("final_state", convertedState);
-+
-+                return null;
++                return converted.concat(string.substring(stateNameEnd));
 +            }
 +        });
 +    }
@@ -2385,6 +2501,300 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        return null;
 +    }
 +}
+diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/custom/V3818_Commands.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/custom/V3818_Commands.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/custom/V3818_Commands.java
+@@ -0,0 +0,0 @@
++package ca.spottedleaf.dataconverter.minecraft.converters.custom;
++
++import ca.spottedleaf.dataconverter.converters.DataConverter;
++import ca.spottedleaf.dataconverter.minecraft.MCDataConverter;
++import ca.spottedleaf.dataconverter.minecraft.MCVersions;
++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
++import ca.spottedleaf.dataconverter.types.ListType;
++import ca.spottedleaf.dataconverter.types.MapType;
++import ca.spottedleaf.dataconverter.types.ObjectType;
++import ca.spottedleaf.dataconverter.util.CommandArgumentUpgrader;
++import com.google.common.base.Suppliers;
++import com.google.gson.JsonArray;
++import com.google.gson.JsonElement;
++import com.google.gson.JsonObject;
++import com.google.gson.JsonParseException;
++import com.google.gson.JsonParser;
++import com.google.gson.JsonPrimitive;
++import com.mojang.brigadier.exceptions.CommandSyntaxException;
++import com.mojang.serialization.Dynamic;
++import com.mojang.serialization.JsonOps;
++import net.minecraft.SharedConstants;
++import net.minecraft.nbt.CompoundTag;
++import net.minecraft.nbt.NbtOps;
++import net.minecraft.nbt.Tag;
++import net.minecraft.nbt.TagParser;
++import net.minecraft.util.GsonHelper;
++import java.util.Iterator;
++import java.util.function.Supplier;
++
++public final class V3818_Commands {
++
++    private static final int VERSION = MCVersions.V24W07A + 1;
++
++    private static final boolean DISABLE_COMMAND_CONVERTER = Boolean.getBoolean("Paper.DisableCommandConverter");
++
++    public static String toCommandFormat(final CompoundTag components) {
++        final StringBuilder ret = new StringBuilder();
++        ret.append('[');
++        for (final Iterator<String> iterator = components.getAllKeys().iterator(); iterator.hasNext();) {
++            final String key = iterator.next();
++            ret.append(key);
++            ret.append('=');
++            ret.append(components.get(key).toString());
++            if (iterator.hasNext()) {
++                ret.append(',');
++            }
++        }
++        ret.append(']');
++
++        return ret.toString();
++    }
++
++    public static JsonElement convertToJson(final Tag tag) {
++        // We don't have conversion utilities, but DFU does...
++
++        return new Dynamic<>(NbtOps.INSTANCE, tag).convert(JsonOps.INSTANCE).getValue();
++    }
++
++    public static void walkComponent(final JsonElement primitive) {
++        if (!(primitive instanceof JsonObject root)) {
++            if (primitive instanceof JsonArray array) {
++                for (final JsonElement component : array) {
++                    walkComponent(component);
++                }
++            }
++            return;
++        }
++
++        final JsonElement clickEventElement = root.get("clickEvent");
++        if (clickEventElement instanceof JsonObject clickEvent) {
++            final JsonElement actionElement = clickEvent.get("action");
++            final JsonElement cmdElement = clickEvent.get("value");
++            if (actionElement instanceof JsonPrimitive action && cmdElement instanceof JsonPrimitive cmd) {
++                final String actionString = action.getAsString();
++                final String cmdString = cmd.getAsString();
++
++                if ((actionString.equals("suggest_command") && cmdString.startsWith("/")) || actionString.equals("run_command")) {
++                    final Object res = MCTypeRegistry.DATACONVERTER_CUSTOM_TYPE_COMMAND.convert(
++                            cmdString, MCVersions.V1_20_4, SharedConstants.getCurrentVersion().getDataVersion().getVersion()
++                    );
++                    if (res instanceof String newCmd) {
++                        clickEvent.addProperty("value", newCmd);
++                    }
++                }
++            }
++        }
++
++        final JsonElement hoverEventElement = root.get("hoverEvent");
++        if (hoverEventElement instanceof JsonObject hoverEvent) {
++            final JsonElement showText = hoverEvent.get("action");
++            if (showText instanceof JsonPrimitive showTextPrimitive && showTextPrimitive.getAsString().equals("show_item")) {
++                final JsonElement contentsElement = hoverEvent.get("contents");
++                if (contentsElement instanceof JsonObject contents) {
++                    final JsonElement idElement = contents.get("id");
++                    final JsonElement tagElement = contents.get("tag");
++
++                    if (idElement instanceof JsonPrimitive idPrimitive) {
++                        final CompoundTag itemNBT = new CompoundTag();
++                        itemNBT.putString("id", idPrimitive.getAsString());
++                        itemNBT.putInt("Count", 1);
++
++                        if (tagElement instanceof JsonPrimitive tagPrimitive) {
++                            try {
++                                final CompoundTag tag = TagParser.parseTag(tagPrimitive.getAsString());
++                                itemNBT.put("tag", tag);
++                            } catch (final CommandSyntaxException ignore) {}
++                        }
++
++                        final CompoundTag converted = MCDataConverter.convertTag(
++                                MCTypeRegistry.ITEM_STACK, itemNBT, MCVersions.V1_20_4,
++                                SharedConstants.getCurrentVersion().getDataVersion().getVersion()
++                        );
++
++                        contents.remove("tag");
++
++                        contents.addProperty("id", converted.getString("id"));
++
++                        if (converted.contains("components", Tag.TAG_COMPOUND)) {
++                            contents.add("components", convertToJson(converted.getCompound("components")));
++                        }
++                    }
++                }
++                final JsonElement valueElement = hoverEvent.get("value");
++                if (valueElement instanceof JsonPrimitive valuePrimitive) {
++                    try {
++                        final CompoundTag itemNBT = TagParser.parseTag(valuePrimitive.getAsString());
++                        if (itemNBT.contains("id", Tag.TAG_STRING)) {
++                            final boolean explicitCount = itemNBT.contains("Count", Tag.TAG_ANY_NUMERIC);
++                            if (!explicitCount) {
++                                itemNBT.putInt("Count", 1);
++                            }
++                            final CompoundTag converted = MCDataConverter.convertTag(
++                                MCTypeRegistry.ITEM_STACK, itemNBT, MCVersions.V1_20_4,
++                                SharedConstants.getCurrentVersion().getDataVersion().getVersion()
++                            );
++
++                            hoverEvent.remove("value");
++
++                            final JsonObject contents = new JsonObject();
++                            hoverEvent.add("contents", contents);
++
++                            contents.addProperty("id", converted.getString("id"));
++                            if (explicitCount) {
++                                contents.addProperty("count", converted.getInt("count"));
++                            }
++
++                            if (converted.contains("components", Tag.TAG_COMPOUND)) {
++                                contents.add("components", convertToJson(converted.getCompound("components")));
++                            }
++                        }
++                    } catch (final CommandSyntaxException ignore) {}
++                }
++            }
++        }
++
++        final JsonElement extra = root.get("extra");
++        if (extra instanceof JsonArray array) {
++            for (final JsonElement component : array) {
++                walkComponent(component);
++            }
++        }
++    }
++
++    private static String walkComponent(final String json) {
++        if (json == null || json.isEmpty()) {
++            return json;
++        }
++
++        try {
++            final JsonElement element = JsonParser.parseString(json);
++            walkComponent(element);
++            return GsonHelper.toStableString(element);
++        } catch (final JsonParseException ex) {
++            return json;
++        }
++    }
++
++    // this is AFTER all the converters for subversion 5, so these run AFTER them
++    public static void register_5() {
++        if (DISABLE_COMMAND_CONVERTER) {
++            return;
++        }
++        // Command is already registered in walker for command blocks
++        MCTypeRegistry.DATACONVERTER_CUSTOM_TYPE_COMMAND.addConverter(new DataConverter<>(VERSION, 5) {
++            private static final Supplier<CommandArgumentUpgrader> COMMAND_UPGRADER = Suppliers.memoize(() ->
++                    CommandArgumentUpgrader.upgrader_1_20_4_to_1_20_5(999));
++
++            @Override
++            public Object convert(final Object data, final long sourceVersion, final long toVersion) {
++                if (!(data instanceof String cmd)) {
++                    return null;
++                }
++                // We use startsWith("/") because we aren't supporting WorldEdit style commands,
++                // and passing the context of whether the use supports leading slash would be high effort low return
++                return COMMAND_UPGRADER.get().upgradeCommandArguments(cmd, cmd.startsWith("/"));
++            }
++        });
++
++        // command is not registered in any walkers for books/signs, and we don't want to do that as we would parse
++        // the json every walk. instead, we create a one time converter to avoid the additional cost of parsing the json
++        // for future updates
++
++        // books
++        // note: at this stage, item is converted to components, so we can use the data components type
++        MCTypeRegistry.DATA_COMPONENTS.addStructureConverter(new DataConverter<>(VERSION, 5) {
++            private static void walkPath(final MapType<String> data, final String path) {
++                final String str = data.getString(path);
++                if (str == null) {
++                    return;
++                }
++
++                final String newStr = walkComponent(str);
++                if (newStr != null) {
++                    data.setString(path, newStr);
++                }
++            }
++
++            private static void walkBookContent(final MapType<String> data, final String path) {
++                if (data == null) {
++                    return;
++                }
++
++                final MapType<String> content = data.getMap(path);
++                if (content == null) {
++                    return;
++                }
++
++                final ListType pages = content.getList("pages", ObjectType.MAP);
++                if (pages == null) {
++                    return;
++                }
++
++                for (int i = 0, len = pages.size(); i < len; ++i) {
++                    final MapType<String> text = pages.getMap(i);
++
++                    walkPath(text, "raw");
++                    walkPath(text, "filtered");
++                }
++            }
++
++            @Override
++            public MapType<String> convert(final MapType<String> data, final long sourceVersion, final long toVersion) {
++                walkBookContent(data, "minecraft:written_book_content");
++                return null;
++            }
++        });
++
++        // signs
++
++        final DataConverter<MapType<String>, MapType<String>> signTileConverter = new DataConverter<>(VERSION, 5) {
++            private static void walkText(final MapType<String> data, final String path) {
++                if (data == null) {
++                    return;
++                }
++
++                final MapType<String> text = data.getMap(path);
++                if (text == null) {
++                    return;
++                }
++
++                final ListType messages = text.getList("messages", ObjectType.STRING);
++                if (messages != null) {
++                    for (int i = 0, len = Math.min(4, messages.size()); i < len; ++i) {
++                        messages.setString(i, walkComponent(messages.getString(i)));
++                    }
++                }
++
++                final ListType filteredMessages = text.getList("filtered_messages", ObjectType.STRING);
++
++                if (filteredMessages != null) {
++                    for (int i = 0, len = Math.min(4, filteredMessages.size()); i < len; ++i) {
++                        filteredMessages.setString(i, walkComponent(filteredMessages.getString(i)));
++                    }
++                }
++            }
++
++            @Override
++            public MapType<String> convert(final MapType<String> data, final long sourceVersion, final long toVersion) {
++                walkText(data, "front_text");
++                walkText(data, "back_text");
++                return null;
++            }
++        };
++
++        MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:sign", signTileConverter);
++        MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:hanging_sign", signTileConverter);
++    }
++}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterAbstractEntityRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterAbstractEntityRename.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
@@ -5382,8 +5792,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        ID_TO_STRING[46] = "MinecartHopper";
 +        ID_TO_STRING[47] = "MinecartSpawner";
 +        ID_TO_STRING[40] = "MinecartCommandBlock";
-+        ID_TO_STRING[48] = "Mob";
-+        ID_TO_STRING[49] = "Monster";
 +        ID_TO_STRING[50] = "Creeper";
 +        ID_TO_STRING[51] = "Skeleton";
 +        ID_TO_STRING[52] = "Spider";
@@ -5432,7 +5840,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 @@ -0,0 +0,0 @@
 +package ca.spottedleaf.dataconverter.minecraft.converters.helpers;
 +
++import ca.spottedleaf.dataconverter.types.ListType;
 +import ca.spottedleaf.dataconverter.types.MapType;
++import ca.spottedleaf.dataconverter.types.ObjectType;
 +import java.util.ArrayList;
 +import java.util.List;
 +import java.util.function.Function;
@@ -5516,8 +5926,23 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        data.setString(key, renamed);
 +    }
 +
-+    private RenameHelper() {}
++    public static void renameListMapItems(final MapType<String> data, final String listPath, final String mapPath,
++                                          final Function<String, String> renamer) {
++        if (data == null) {
++            return;
++        }
 +
++        final ListType list = data.getList(listPath, ObjectType.MAP);
++        if (list == null) {
++            return;
++        }
++
++        for (int i = 0, len = list.size(); i < len; ++i) {
++            RenameHelper.renameString(list.getMap(i), mapPath, renamer);
++        }
++    }
++
++    private RenameHelper() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemname/ConverterAbstractItemRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemname/ConverterAbstractItemRename.java
 new file mode 100644
@@ -5543,6 +5968,47 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +}
+diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterEnchantmentsRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterEnchantmentsRename.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterEnchantmentsRename.java
+@@ -0,0 +0,0 @@
++package ca.spottedleaf.dataconverter.minecraft.converters.itemstack;
++
++import ca.spottedleaf.dataconverter.converters.DataConverter;
++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper;
++import ca.spottedleaf.dataconverter.types.MapType;
++import java.util.function.Function;
++
++public final class ConverterEnchantmentsRename extends DataConverter<MapType<String>, MapType<String>> {
++
++    private final Function<String, String> renamer;
++
++    public ConverterEnchantmentsRename(final int toVersion, final Function<String, String> renamer) {
++        this(toVersion, 0, renamer);
++    }
++
++    public ConverterEnchantmentsRename(final int toVersion, final int versionStep, final Function<String, String> renamer) {
++        super(toVersion, versionStep);
++
++        this.renamer = renamer;
++    }
++
++    @Override
++    public MapType<String> convert(final MapType<String> data, final long sourceVersion, final long toVersion) {
++        final MapType<String> tag = data.getMap("tag");
++
++        if (tag == null) {
++            return null;
++        }
++
++        RenameHelper.renameListMapItems(tag, "Enchantments", "id", this.renamer);
++        RenameHelper.renameListMapItems(tag, "StoredEnchantments", "id", this.renamer);
++
++        return null;
++    }
++}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenItemStack.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenItemStack.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
@@ -6102,6 +6568,1256 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        return null;
 +    }
 +}
+diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterItemStackToDataComponents.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterItemStackToDataComponents.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterItemStackToDataComponents.java
+@@ -0,0 +0,0 @@
++package ca.spottedleaf.dataconverter.minecraft.converters.itemstack;
++
++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper;
++import ca.spottedleaf.dataconverter.minecraft.util.ComponentUtils;
++import ca.spottedleaf.dataconverter.minecraft.versions.V3818;
++import ca.spottedleaf.dataconverter.types.ListType;
++import ca.spottedleaf.dataconverter.types.MapType;
++import ca.spottedleaf.dataconverter.types.ObjectType;
++import ca.spottedleaf.dataconverter.types.TypeUtil;
++import ca.spottedleaf.dataconverter.util.NamespaceUtil;
++import net.minecraft.util.Mth;
++import java.util.Arrays;
++import java.util.HashSet;
++import java.util.Set;
++
++public final class ConverterItemStackToDataComponents {
++
++    private static final int TOOLTIP_FLAG_HIDE_ENCHANTMENTS = 1 << 0;
++    private static final int TOOLTIP_FLAG_HIDE_MODIFIERS = 1 << 1;
++    private static final int TOOLTIP_FLAG_HIDE_UNBREAKABLE = 1 << 2;
++    private static final int TOOLTIP_FLAG_HIDE_CAN_DESTROY = 1 << 3;
++    private static final int TOOLTIP_FLAG_HIDE_CAN_PLACE = 1 << 4;
++    private static final int TOOLTIP_FLAG_HIDE_ADDITIONAL = 1 << 5;
++    private static final int TOOLTIP_FLAG_HIDE_DYE = 1 << 6;
++    private static final int TOOLTIP_FLAG_HIDE_UPGRADES = 1 << 7;
++
++    private static final int DEFAULT_LEATHER_COLOUR = (160 << 16) | (101 << 8) | (64 << 0); // r, g, b
++
++    private static final String[] BUCKETED_MOB_TAGS = new String[] {
++            "NoAI",
++            "Silent",
++            "NoGravity",
++            "Glowing",
++            "Invulnerable",
++            "Health",
++            "Age",
++            "Variant",
++            "HuntingCooldown",
++            "BucketVariantTag"
++    };
++    private static final Set<String> BOOLEAN_BLOCK_STATE_PROPERTIES = new HashSet<>(
++            Arrays.asList(
++                    "attached",
++                    "bottom",
++                    "conditional",
++                    "disarmed",
++                    "drag",
++                    "enabled",
++                    "extended",
++                    "eye",
++                    "falling",
++                    "hanging",
++                    "has_bottle_0",
++                    "has_bottle_1",
++                    "has_bottle_2",
++                    "has_record",
++                    "has_book",
++                    "inverted",
++                    "in_wall",
++                    "lit",
++                    "locked",
++                    "occupied",
++                    "open",
++                    "persistent",
++                    "powered",
++                    "short",
++                    "signal_fire",
++                    "snowy",
++                    "triggered",
++                    "unstable",
++                    "waterlogged",
++                    "berries",
++                    "bloom",
++                    "shrieking",
++                    "can_summon",
++                    "up",
++                    "down",
++                    "north",
++                    "east",
++                    "south",
++                    "west",
++                    "slot_0_occupied",
++                    "slot_1_occupied",
++                    "slot_2_occupied",
++                    "slot_3_occupied",
++                    "slot_4_occupied",
++                    "slot_5_occupied",
++                    "cracked",
++                    "crafting"
++            )
++    );
++    private static final String[] MAP_DECORATION_CONVERSION_TABLE = new String[34];
++    static {
++        MAP_DECORATION_CONVERSION_TABLE[0] = "player";
++        MAP_DECORATION_CONVERSION_TABLE[1] = "frame";
++        MAP_DECORATION_CONVERSION_TABLE[2] = "red_marker";
++        MAP_DECORATION_CONVERSION_TABLE[3] = "blue_marker";
++        MAP_DECORATION_CONVERSION_TABLE[4] = "target_x";
++        MAP_DECORATION_CONVERSION_TABLE[5] = "target_point";
++        MAP_DECORATION_CONVERSION_TABLE[6] = "player_off_map";
++        MAP_DECORATION_CONVERSION_TABLE[7] = "player_off_limits";
++        MAP_DECORATION_CONVERSION_TABLE[8] = "mansion";
++        MAP_DECORATION_CONVERSION_TABLE[9] = "monument";
++        MAP_DECORATION_CONVERSION_TABLE[10] = "banner_white";
++        MAP_DECORATION_CONVERSION_TABLE[11] = "banner_orange";
++        MAP_DECORATION_CONVERSION_TABLE[12] = "banner_magenta";
++        MAP_DECORATION_CONVERSION_TABLE[13] = "banner_light_blue";
++        MAP_DECORATION_CONVERSION_TABLE[14] = "banner_yellow";
++        MAP_DECORATION_CONVERSION_TABLE[15] = "banner_lime";
++        MAP_DECORATION_CONVERSION_TABLE[16] = "banner_pink";
++        MAP_DECORATION_CONVERSION_TABLE[17] = "banner_gray";
++        MAP_DECORATION_CONVERSION_TABLE[18] = "banner_light_gray";
++        MAP_DECORATION_CONVERSION_TABLE[19] = "banner_cyan";
++        MAP_DECORATION_CONVERSION_TABLE[20] = "banner_purple";
++        MAP_DECORATION_CONVERSION_TABLE[21] = "banner_blue";
++        MAP_DECORATION_CONVERSION_TABLE[22] = "banner_brown";
++        MAP_DECORATION_CONVERSION_TABLE[23] = "banner_green";
++        MAP_DECORATION_CONVERSION_TABLE[24] = "banner_red";
++        MAP_DECORATION_CONVERSION_TABLE[25] = "banner_black";
++        MAP_DECORATION_CONVERSION_TABLE[26] = "red_x";
++        MAP_DECORATION_CONVERSION_TABLE[27] = "village_desert";
++        MAP_DECORATION_CONVERSION_TABLE[28] = "village_plains";
++        MAP_DECORATION_CONVERSION_TABLE[29] = "village_savanna";
++        MAP_DECORATION_CONVERSION_TABLE[30] = "village_snowy";
++        MAP_DECORATION_CONVERSION_TABLE[31] = "village_taiga";
++        MAP_DECORATION_CONVERSION_TABLE[32] = "jungle_temple";
++        MAP_DECORATION_CONVERSION_TABLE[33] = "swamp_hut";
++    }
++
++    private static String convertMapDecorationId(final int type) {
++        return type >= 0 && type < MAP_DECORATION_CONVERSION_TABLE.length ? MAP_DECORATION_CONVERSION_TABLE[type] : MAP_DECORATION_CONVERSION_TABLE[0];
++    }
++
++    private static void convertBlockStateProperties(final MapType<String> properties) {
++        // convert values stored as boolean/integer to string
++        for (final String key : properties.keys()) {
++            final Object value = properties.getGeneric(key);
++            if (value instanceof Number number) {
++                if (BOOLEAN_BLOCK_STATE_PROPERTIES.contains(key)) {
++                    properties.setString(key, Boolean.toString(number.byteValue() != (byte)0));
++                } else {
++                    properties.setString(key, number.toString());
++                }
++            }
++        }
++    }
++
++    private static void convertTileEntity(final MapType<String> tileEntity, final TransientItemStack transientItem) {
++        final Object lock = tileEntity.getGeneric("Lock");
++        if (lock != null) {
++            tileEntity.remove("Lock");
++            transientItem.componentSetGeneric("minecraft:lock", lock);
++        }
++
++        final Object lootTable = tileEntity.getGeneric("LootTable");
++        if (lootTable != null) {
++            final MapType<String> containerLoot = tileEntity.getTypeUtil().createEmptyMap();
++            transientItem.componentSetMap("minecraft:container_loot", containerLoot);
++
++            containerLoot.setGeneric("loot_table", lootTable);
++
++            final long seed = tileEntity.getLong("LootTableSeed", 0L);
++            if (seed != 0L) {
++                containerLoot.setLong("seed", seed);
++            }
++
++            tileEntity.remove("LootTable");
++            tileEntity.remove("LootTableSeed");
++        }
++
++        final String id = NamespaceUtil.correctNamespace(tileEntity.getString("id", ""));
++
++        switch (id) {
++            case "minecraft:skull": {
++                final Object noteBlockSound = tileEntity.getGeneric("note_block_sound");
++                if (noteBlockSound != null) {
++                    tileEntity.remove("note_block_sound");
++                    transientItem.componentSetGeneric("minecraft:note_block_sound", noteBlockSound);
++                }
++
++                break;
++            }
++            case "minecraft:decorated_pot": {
++                final Object sherds = tileEntity.getGeneric("sherds");
++                if (sherds != null) {
++                    tileEntity.remove("sherds");
++                    transientItem.componentSetGeneric("minecraft:pot_decorations", sherds);
++                }
++
++                final Object item = tileEntity.getGeneric("item");
++                if (item != null) {
++                    tileEntity.remove("item");
++
++                    final ListType container = tileEntity.getTypeUtil().createEmptyList();
++                    transientItem.componentSetList("minecraft:container", container);
++
++                    final MapType<String> wrappedItem = tileEntity.getTypeUtil().createEmptyMap();
++                    container.addMap(wrappedItem);
++
++                    wrappedItem.setInt("slot", 0);
++                    wrappedItem.setGeneric("item", item);
++                }
++
++                break;
++            }
++            case "minecraft:banner": {
++                final Object patterns = tileEntity.getGeneric("patterns");
++                if (patterns != null) {
++                    tileEntity.remove("patterns");
++
++                    transientItem.componentSetGeneric("minecraft:banner_patterns", patterns);
++                }
++
++                final Number base = tileEntity.getNumber("Base");
++                if (base != null) {
++                    tileEntity.remove("Base");
++
++                    transientItem.componentSetString("minecraft:base_color", V3818.getBannerColour(base.intValue()));
++                }
++
++                break;
++            }
++
++            case "minecraft:shulker_box":
++            case "minecraft:chest":
++            case "minecraft:trapped_chest":
++            case "minecraft:furnace":
++            case "minecraft:ender_chest":
++            case "minecraft:dispenser":
++            case "minecraft:dropper":
++            case "minecraft:brewing_stand":
++            case "minecraft:hopper":
++            case "minecraft:barrel":
++            case "minecraft:smoker":
++            case "minecraft:blast_furnace":
++            case "minecraft:campfire":
++            case "minecraft:chiseled_bookshelf":
++            case "minecraft:crafter": {
++                final ListType items = tileEntity.getList("Items", ObjectType.MAP);
++                tileEntity.remove("Items");
++                if (items != null && items.size() > 1) {
++                    transientItem.componentSetList("minecraft:container", items);
++
++                    for (int i = 0, len = items.size(); i < len; ++i) {
++                        final MapType<String> item = items.getMap(i);
++                        final int slot = (int)item.getByte("Slot", (byte)0) & 0xFF;
++                        item.remove("Slot");
++
++                        final MapType<String> wrappedItem = item.getTypeUtil().createEmptyMap();
++                        items.setMap(i, wrappedItem);
++
++                        wrappedItem.setInt("slot", slot);
++                        wrappedItem.setMap("item", item);
++                    }
++                }
++
++                break;
++            }
++
++            case "minecraft:beehive": {
++                final Object bees = tileEntity.getGeneric("bees");
++                if (bees != null) {
++                    tileEntity.remove("bees");
++
++                    transientItem.componentSetGeneric("minecraft:bees", bees);
++                }
++                break;
++            }
++        }
++    }
++
++    private static void convertEnchantments(final TransientItemStack transientItem, final TypeUtil type,
++                                            final String tagKey, final String componentKey,
++                                            final boolean hideToolTip) {
++        final ListType enchantments = transientItem.tagRemoveList(tagKey, ObjectType.MAP);
++        if (enchantments == null || enchantments.size() == 0) {
++            if (hideToolTip) {
++                final MapType<String> newEnchants = type.createEmptyMap();
++                transientItem.componentSetMap(componentKey, newEnchants);
++
++                newEnchants.setMap("levels", type.createEmptyMap());
++                newEnchants.setBoolean("show_in_tooltip", false);
++            }
++        } else {
++            final MapType<String> newEnchantments = type.createEmptyMap();
++
++            for (int i = 0, len = enchantments.size(); i < len; ++i) {
++                final MapType<String> enchantment = enchantments.getMap(i);
++
++                final String id = enchantment.getString("id");
++                final Number level = enchantment.getNumber("lvl");
++
++                if (id == null || level == null) {
++                    continue;
++                }
++
++                newEnchantments.setInt(id, Mth.clamp(level.intValue(), 0, 0xFF));
++            }
++
++            if (!newEnchantments.isEmpty() || hideToolTip) {
++                final MapType<String> newEnchants = type.createEmptyMap();
++                transientItem.componentSetMap(componentKey, newEnchants);
++
++                newEnchants.setMap("levels", newEnchantments);
++                if (hideToolTip) {
++                    newEnchants.setBoolean("show_in_tooltip", false);
++                }
++            }
++        }
++
++        if (enchantments != null && enchantments.size() == 0) {
++            transientItem.componentSetBoolean("minecraft:enchantment_glint_override", true);
++        }
++    }
++
++    private static void convertDisplay(final TransientItemStack transientItem, final TypeUtil type, final int flags) {
++        final MapType<String> display = transientItem.tag.getMap("display");
++
++        if (display != null) {
++            final Object name = display.getGeneric("Name");
++            if (name != null) {
++                display.remove("Name");
++
++                transientItem.componentSetGeneric("minecraft:custom_name", name);
++            }
++
++            final Object lore = display.getGeneric("Lore");
++            if (lore != null) {
++                display.remove("Lore");
++
++                transientItem.componentSetGeneric("minecraft:lore", lore);
++            }
++        }
++
++        final Number color = display == null ? null : display.getNumber("color");
++        final boolean hideDye = (flags & TOOLTIP_FLAG_HIDE_DYE) != 0;
++
++        if (hideDye || color != null) {
++            if (color != null) {
++                display.remove("color");
++            }
++
++            final MapType<String> dyedColor = type.createEmptyMap();
++            transientItem.componentSetMap("minecraft:dyed_color", dyedColor);
++
++            dyedColor.setInt("rgb", color == null ? DEFAULT_LEATHER_COLOUR : color.intValue());
++            if (hideDye) {
++                dyedColor.setBoolean("show_in_tooltip", false);
++            }
++        }
++
++        final Object locName = display == null ? null : display.getGeneric("LocName");
++        if (locName != null) {
++            display.remove("LocName");
++
++            if (locName instanceof String locNameString) {
++                transientItem.componentSetString("minecraft:item_name", ComponentUtils.createTranslatableComponent(locNameString));
++            }
++        }
++
++        if (display != null && "minecraft:filled_map".equals(transientItem.id)) {
++            final Object mapColor = display.getGeneric("MapColor");
++            if (mapColor != null) {
++                display.remove("MapColor");
++
++                transientItem.componentSetGeneric("minecraft:map_color", mapColor);
++            }
++        }
++
++        // mirror behavior of fixSubTag
++        if (display != null && display.isEmpty()) {
++            transientItem.tagRemoveMap("display");
++        }
++    }
++
++    public static MapType<String> convertBlockStatePredicate(final String value, final TypeUtil type) {
++        final int propertyStart = value.indexOf('[');
++        final int nbtStart = value.indexOf('{');
++        int blockNameEnd = value.length();
++
++        if (propertyStart != -1) {
++            blockNameEnd = propertyStart;
++        }
++        if (nbtStart != -1) {
++            blockNameEnd = Math.min(blockNameEnd, nbtStart);
++        }
++
++        final MapType<String> ret = type.createEmptyMap();
++
++        final String blockName = value.substring(0, blockNameEnd);
++
++        // string is fine here, the underlying type accepts string AND list under the same name...
++        ret.setString("blocks", blockName.trim());
++
++        if (propertyStart != -1) {
++            // unlike DFU, set the fromIndex so that on malformed data we do not IOOBE
++            final int propertyEnd = value.indexOf(']', propertyStart + 1);
++            if (propertyEnd != -1) {
++                final MapType<String> state = type.createEmptyMap();
++                ret.setMap("state", state);
++
++                for (final String property : value.substring(propertyStart + 1, propertyEnd).split(",")) {
++                    final int separatorIdx = property.indexOf('=');
++                    if (separatorIdx == -1) {
++                        continue;
++                    }
++
++                    final String propertyKey = property.substring(0, separatorIdx).trim();
++                    final String propertyValue = property.substring(separatorIdx + 1);
++
++                    state.setString(propertyKey, propertyValue);
++                }
++            }
++        }
++
++        if (nbtStart != -1) {
++            // unlike DFU, set the fromIndex so that on malformed data we do not IOOBE
++            final int nbtEnd = value.indexOf('}', nbtStart + 1);
++            if (nbtEnd != -1) {
++                // note: want to include { and }
++                ret.setString("nbt", value.substring(nbtStart, nbtEnd + 1));
++            }
++        }
++
++        return ret;
++    }
++
++    private static void convertBlockStatePredicates(final TransientItemStack item, final TypeUtil type,
++                                                    final String tagKey, final String componentKey,
++                                                    final boolean hideInTooltip) {
++        final ListType blocks = item.tagRemoveListUnchecked(tagKey);
++        if (blocks == null) {
++            return;
++        }
++
++        final MapType<String> blockPredicates = type.createEmptyMap();
++        item.componentSetMap(componentKey, blockPredicates);
++
++        if (hideInTooltip) {
++            blockPredicates.setBoolean("show_in_tooltip", false);
++        }
++
++        final ListType predicates = type.createEmptyList();
++        blockPredicates.setList("predicates", predicates);
++
++        for (int i = 0, len = blocks.size(); i < len; ++i) {
++            final Object block = blocks.getGeneric(i);
++            if (!(block instanceof String blockString)) {
++                // cannot type error here, if block is not a string then nothing in `blocks` is as they have the same type
++                predicates.addGeneric(block);
++                continue;
++            }
++
++            final MapType<String> predicate = convertBlockStatePredicate(blockString, type);
++
++            predicates.addMap(predicate);
++        }
++    }
++
++    private static void convertAdventureMode(final TransientItemStack item, final TypeUtil type, final int flags) {
++        convertBlockStatePredicates(
++                item, type, "CanDestroy", "minecraft:can_break",
++                (flags & TOOLTIP_FLAG_HIDE_CAN_DESTROY) != 0
++        );
++        convertBlockStatePredicates(
++                item, type, "CanPlaceOn", "minecraft:can_place_on",
++                (flags & TOOLTIP_FLAG_HIDE_CAN_PLACE) != 0
++        );
++    }
++
++    private static void copy(final MapType<String> src, final String srcKey, final MapType<String> dst, final String dstKey) {
++        if (src == null || dst == null) {
++            return;
++        }
++
++        final Object srcValue = src.getGeneric(srcKey);
++        if (srcValue != null) {
++            dst.setGeneric(dstKey, srcValue);
++        }
++    }
++
++    private static MapType<String> convertAttribute(final Object inputGeneric, final TypeUtil type) {
++        final MapType<String> input = inputGeneric instanceof MapType<?> casted ? (MapType<String>)casted : null;
++
++        final MapType<String> ret = type.createEmptyMap();
++        ret.setString("name", "");
++        ret.setDouble("amount", 0.0);
++        ret.setString("operation", "add_value");
++
++        copy(input, "AttributeName", ret, "type");
++        copy(input, "Slot", ret, "slot");
++        copy(input, "UUID", ret, "uuid");
++        copy(input, "Name", ret, "name");
++        copy(input, "Amount", ret, "amount");
++
++        // note: no type check on hasKey
++        if (input != null && input.hasKey("Operation")) {
++            final String operation;
++            switch (input.getInt("Operation", 0)) {
++                case 1: {
++                    operation = "add_multiplied_base";
++                    break;
++                }
++                case 2: {
++                    operation = "add_multiplied_total";
++                    break;
++                }
++                default: {
++                    operation = "add_value";
++                    break;
++                }
++            }
++            ret.setString("operation", operation);
++        }
++
++        return ret;
++    }
++
++    private static void convertAttributes(final TransientItemStack item, final TypeUtil type, final int flags) {
++        final ListType attributes = item.tagRemoveListUnchecked("AttributeModifiers");
++        final ListType newAttributes = type.createEmptyList();
++
++        if (attributes != null) {
++            for (int i = 0, len = attributes.size(); i < len; ++i) {
++                newAttributes.addMap(convertAttribute(attributes.getGeneric(i), type));
++            }
++        }
++
++        final boolean hideModifiers = (flags & TOOLTIP_FLAG_HIDE_MODIFIERS) != 0;
++        if (newAttributes.size() > 0 || hideModifiers) {
++            final MapType<String> newModifiers = type.createEmptyMap();
++            item.componentSetMap("minecraft:attribute_modifiers", newModifiers);
++
++            newModifiers.setList("modifiers", newAttributes);
++            if (hideModifiers) {
++                newModifiers.setBoolean("show_in_tooltip", false);
++            }
++        }
++    }
++
++    private static void convertMap(final TransientItemStack item, final TypeUtil type) {
++        item.tagMigrateToComponent("map", "minecraft:map_id");
++
++        final ListType decorations = item.tagRemoveListUnchecked("Decorations");
++        if (decorations != null) {
++            final MapType<String> newDecorations = type.createEmptyMap();
++
++            for (int i = 0, len = decorations.size(); i < len; ++i) {
++                final Object decorationGeneric = decorations.getGeneric(i);
++
++                final MapType<String> decoration = decorationGeneric instanceof MapType<?> casted ? (MapType<String>)casted : null;
++
++                // note: getForcedString mirrors DFU converting to string for key
++                final String id = decoration == null ? "" : decoration.getForcedString("id", "");
++                if (newDecorations.hasKey(id)) {
++                    // note: never replace existing decorations by id
++                    continue;
++                }
++
++                final int typeId = decoration == null ? 0 : decoration.getInt("type", 0);
++                final double x = decoration == null ? 0.0 : decoration.getDouble("x", 0.0);
++                final double z = decoration == null ? 0.0 : decoration.getDouble("z", 0.0);
++                final float rot = decoration == null ? 0.0f : (float)decoration.getDouble("rot", 0.0);
++
++                final MapType<String> newDecoration = type.createEmptyMap();
++                newDecorations.setMap(id, newDecoration);
++
++                newDecoration.setString("type", convertMapDecorationId(typeId));
++                newDecoration.setDouble("x", x);
++                newDecoration.setDouble("z", z);
++                newDecoration.setFloat("rotation", rot);
++            }
++
++            if (!newDecorations.isEmpty()) {
++                item.componentSetMap("minecraft:map_decorations", newDecorations);
++            }
++        }
++    }
++
++    private static void convertPotion(final TransientItemStack item, final TypeUtil type) {
++        final MapType<String> potionContents = type.createEmptyMap();
++
++        final String potion = item.tagRemoveString("Potion");
++
++        if (potion != null && !"minecraft:empty".equals(potion)) {
++            potionContents.setString("potion", potion);
++        }
++
++        item.migrateTagTo("CustomPotionColor", potionContents, "custom_color");
++        item.migrateTagTo("custom_potion_effects", potionContents, "custom_effects");
++
++        if (!potionContents.isEmpty()) {
++            item.componentSetMap("minecraft:potion_contents", potionContents);
++        }
++    }
++
++    private static MapType<String> makeFilteredText(final String raw, final String filtered, final TypeUtil type) {
++        final MapType<String> ret = type.createEmptyMap();
++
++        ret.setString("raw", raw);
++        if (filtered != null) {
++            ret.setString("filtered", filtered);
++        }
++
++        return ret;
++    }
++
++    private static ListType convertBookPages(final TransientItemStack item, final TypeUtil type) {
++        final ListType oldPages = item.tagRemoveListUnchecked("pages");
++
++        final MapType<String> filteredPages = item.tagRemoveMap("filtered_pages");
++
++        if (oldPages == null || oldPages.size() == 0) {
++            return null;
++        }
++
++        final ListType ret = type.createEmptyList();
++
++        for (int i = 0, len = oldPages.size(); i < len; ++i) {
++            final String page = oldPages.getGeneric(i) instanceof String str ? str : "";
++            final String filtered = filteredPages == null ? null : filteredPages.getString(Integer.toString(i));
++
++            ret.addMap(makeFilteredText(page, filtered, type));
++        }
++
++        return ret;
++    }
++
++    private static void convertWritableBook(final TransientItemStack item, final TypeUtil type) {
++        final ListType pages = convertBookPages(item, type);
++        if (pages != null) {
++            final MapType<String> bookContent = type.createEmptyMap();
++            item.componentSetMap("minecraft:writable_book_content", bookContent);
++
++            bookContent.setList("pages", pages);
++        }
++    }
++
++    private static void convertWrittenBook(final TransientItemStack item, final TypeUtil type) {
++        final ListType pages = convertBookPages(item, type);
++
++        final MapType<String> bookContent = type.createEmptyMap();
++        item.componentSetMap("minecraft:written_book_content", bookContent);
++        if (pages != null) {
++            bookContent.setList("pages", pages);
++        }
++
++        final String title = item.tagRemoveString("title", "");
++        final String filteredTitle = item.tagRemoveString("filtered_title");
++
++        bookContent.setMap("title", makeFilteredText(title, filteredTitle, type));
++
++        item.migrateTagTo("author", bookContent, "author");
++        item.migrateTagTo("resolved", bookContent, "resolved");
++        item.migrateTagTo("generation", bookContent, "generation");
++    }
++
++    private static void convertMobBucket(final TransientItemStack item, final TypeUtil type) {
++        final MapType<String> bucketEntityData = type.createEmptyMap();
++
++        for (final String oldKey : BUCKETED_MOB_TAGS) {
++            item.migrateTagTo(oldKey, bucketEntityData, oldKey);
++        }
++
++        if (!bucketEntityData.isEmpty()) {
++            item.componentSetMap("minecraft:bucket_entity_data", bucketEntityData);
++        }
++    }
++
++    private static void convertCompass(final TransientItemStack item, final TypeUtil type) {
++        final Object lodestonePos = item.tagRemoveGeneric("LodestonePos");
++        final Object lodestoneDim = item.tagRemoveGeneric("LodestoneDimension");
++
++        if (lodestonePos == null && lodestoneDim == null) {
++            return;
++        }
++
++        final MapType<String> lodestoneTracker = type.createEmptyMap();
++        item.componentSetMap("minecraft:lodestone_tracker", lodestoneTracker);
++
++        if (lodestonePos != null && lodestoneDim != null) {
++            final MapType<String> target = type.createEmptyMap();
++            lodestoneTracker.setMap("target", target);
++
++            target.setGeneric("pos", lodestonePos);
++            target.setGeneric("dimension", lodestoneDim);
++        }
++
++        final boolean tracked = item.tagRemoveBoolean("LodestoneTracked", true);
++        if (!tracked) {
++            lodestoneTracker.setBoolean("tracked", false);
++        }
++    }
++
++    private static void convertFireworkExplosion(final Object inputGeneric) {
++        if (!(inputGeneric instanceof MapType<?>)) {
++            return;
++        }
++
++        final MapType<String> input = (MapType<String>)inputGeneric;
++
++        RenameHelper.renameSingle(input, "Colors", "colors");
++        RenameHelper.renameSingle(input, "FadeColors", "fade_colors");
++        RenameHelper.renameSingle(input, "Trail", "has_trail");
++        RenameHelper.renameSingle(input, "Flicker", "has_twinkle");
++
++        final int type = input.getInt("Type", 0);
++        input.remove("Type");
++
++        final String newType;
++        switch (type) {
++            case 1: {
++                newType = "large_ball";
++                break;
++            }
++            case 2: {
++                newType = "star";
++                break;
++            }
++            case 3: {
++                newType = "creeper";
++                break;
++            }
++            case 4: {
++                newType = "burst";
++                break;
++            }
++            default: {
++                newType = "small_ball";
++                break;
++            }
++        }
++
++        input.setString("shape", newType);
++    }
++
++    private static void convertFireworkRocket(final TransientItemStack item, final TypeUtil type) {
++        // adhere to fixSubTag(true) behavior
++        final Object fireworksGeneric = item.tag.getGeneric("Fireworks");
++        if (fireworksGeneric == null) {
++            return;
++        }
++
++        if (!(fireworksGeneric instanceof MapType<?>)) {
++            final MapType<String> newFireworks = type.createEmptyMap();
++            item.componentSetMap("minecraft:fireworks", newFireworks);
++
++            newFireworks.setList("explosions", type.createEmptyList());
++            newFireworks.setByte("flight_duration", (byte)0);
++
++            return;
++        }
++
++        final MapType<String> fireworks = (MapType<String>)fireworksGeneric;
++
++        final MapType<String> newFireworks = type.createEmptyMap();
++        item.componentSetMap("minecraft:fireworks", newFireworks);
++
++        final int flight = fireworks.getInt("Flight", 0);
++        newFireworks.setByte("flight_duration", (byte)flight);
++
++        final ListType explosions = fireworks.getListUnchecked("Explosions", type.createEmptyList());
++        newFireworks.setList("explosions", explosions);
++
++        for (int i = 0, len = explosions.size(); i < len; ++i) {
++            convertFireworkExplosion(explosions.getGeneric(i));
++        }
++
++        fireworks.remove("Explosions");
++        fireworks.remove("Flight");
++        if (fireworks.isEmpty()) {
++            item.tag.remove("Fireworks");
++        }
++    }
++
++    private static Object copyGeneric(final Object value, final TypeUtil type) {
++        if (value == null || value instanceof Number || value instanceof String) {
++            return value;
++        }
++        if (value instanceof MapType<?> mapType) {
++            return mapType.copy();
++        }
++        if (value instanceof ListType listType) {
++            return listType.copy();
++        }
++        // rest of the cases can take the slow path
++
++        final ListType dummy = type.createEmptyList();
++        dummy.addGeneric(value);
++
++        return dummy.copy().getGeneric(0);
++    }
++
++    private static void convertFireworkStar(final TransientItemStack item, final TypeUtil type) {
++        // note: adhere to fixSubTag(true) behavior
++        final Object explosionGeneric = item.tag.getGeneric("Explosion");
++        if (explosionGeneric == null) {
++            return;
++        }
++
++        if (!(explosionGeneric instanceof MapType<?>)) {
++            // important that we copy the generic value when not moving it
++            item.componentSetGeneric("minecraft:firework_explosion", copyGeneric(explosionGeneric, type));
++            return;
++        }
++
++        final MapType<String> explosion = (MapType<String>)explosionGeneric;
++
++        final MapType<String> explosionCopy = explosion.copy();
++        item.componentSetGeneric("minecraft:firework_explosion", explosionCopy);
++        convertFireworkExplosion(explosionCopy);
++
++        explosion.remove("Type");
++        explosion.remove("Colors");
++        explosion.remove("FadeColors");
++        explosion.remove("Trail");
++        explosion.remove("Flicker");
++
++        if (explosion.isEmpty()) {
++            item.tag.remove("Explosion");
++        }
++    }
++
++    private static boolean isValidPlayerName(final String name) {
++        if (name.length() > 16) {
++            return false;
++        }
++
++        for (int i = 0, len = name.length(); i < len; ++i) {
++            final char character = name.charAt(i);
++            if (character <= 0x20 || character >= 0x7F) { // printable ascii
++                return false;
++            }
++        }
++
++        return true;
++    }
++
++    private static ListType convertProperties(final MapType<String> properties, final TypeUtil type) {
++        final ListType ret = type.createEmptyList();
++
++        for (final String propertyKey : properties.keys()) {
++            final ListType propertyValues = properties.getListUnchecked(propertyKey);
++
++            if (propertyValues == null) {
++                continue;
++            }
++
++            for (int i = 0, len = propertyValues.size(); i < len; ++i) {
++                final MapType<String> property = propertyValues.getGeneric(i) instanceof MapType<?> casted ? (MapType<String>)casted : null;
++
++                final String value = property == null ? "" : property.getString("Value", "");
++                final String signature = property == null ? null : property.getString("Signature");
++
++                final MapType<String> newProperty = type.createEmptyMap();
++                ret.addMap(newProperty);
++
++                newProperty.setString("name", propertyKey);
++                newProperty.setString("value", value);
++                if (signature != null) {
++                    newProperty.setString("signature", signature);
++                }
++            }
++        }
++
++        return ret;
++    }
++
++    public static MapType<String> convertProfile(final Object inputGeneric, final TypeUtil type) {
++        final MapType<String> ret = type.createEmptyMap();
++
++        if (inputGeneric instanceof String name) {
++            if (!isValidPlayerName(name)) {
++                return ret;
++            }
++
++            ret.setString("name", name);
++
++            return ret;
++        }
++
++        final MapType<String> input = inputGeneric instanceof MapType<?> casted ? (MapType<String>)casted : null;
++
++        final String name = input == null ? "" : input.getString("Name", "");
++
++        if (isValidPlayerName(name)) {
++            ret.setString("name", name);
++        }
++
++        final Object id = input == null ? null : input.getGeneric("Id");
++
++        if (id != null) {
++            ret.setGeneric("id", id);
++        }
++
++        final MapType<String> properties = input == null ? null : input.getMap("Properties");
++        if (properties != null && !properties.isEmpty()) {
++            ret.setList("properties", convertProperties(properties, type));
++        }
++
++        return ret;
++    }
++
++    private static void convertSukll(final TransientItemStack item, final TypeUtil type) {
++        final Object skullOwnerGeneric = item.tagRemoveGeneric("SkullOwner");
++        if (skullOwnerGeneric == null) {
++            return;
++        }
++
++        item.componentSetMap("minecraft:profile", convertProfile(skullOwnerGeneric, type));
++    }
++
++    // input is unmodified
++    public static MapType<String> convertItem(final MapType<String> input) {
++        if (!input.hasKey("id", ObjectType.STRING) || !input.hasKey("Count", ObjectType.NUMBER)) {
++            return input.copy();
++        }
++
++        final TypeUtil type = input.getTypeUtil();
++
++        final TransientItemStack item = new TransientItemStack(input);
++
++        item.tagMigrateToComponent("Damage", "minecraft:damage", 0);
++        item.tagMigrateToComponent("RepairCost", "minecraft:repair_cost", 0);
++        item.tagMigrateToComponent("CustomModelData", "minecraft:custom_model_data");
++
++        final MapType<String> blockStateProperties = item.tagRemoveMap("BlockStateTag");
++        if (blockStateProperties != null) {
++            item.componentSetMap("minecraft:block_state", blockStateProperties);
++            convertBlockStateProperties(blockStateProperties);
++        }
++
++        item.tagMigrateToComponent("EntityTag", "minecraft:entity_data");
++
++        final MapType<String> tileEntityTag = item.tagRemoveMap("BlockEntityTag");
++        if (tileEntityTag != null) {
++            convertTileEntity(tileEntityTag, item);
++
++            if (tileEntityTag.size() > 1 || (tileEntityTag.size() == 1 && !tileEntityTag.hasKey("id"))) {
++                item.componentSetMap("minecraft:block_entity_data", tileEntityTag);
++            }
++        }
++
++        final int flags = item.tagRemoveInt("HideFlags", 0);
++
++        if (item.tagRemoveInt("Unbreakable", 0) != 0) {
++            final MapType<String> unbreakable = type.createEmptyMap();
++            item.componentSetMap("minecraft:unbreakable", unbreakable);
++            if ((flags & TOOLTIP_FLAG_HIDE_UNBREAKABLE) != 0) {
++                unbreakable.setBoolean("show_in_tooltip", false);
++            }
++        }
++
++        convertEnchantments(
++                item, type, "Enchantments", "minecraft:enchantments",
++                (flags & TOOLTIP_FLAG_HIDE_ENCHANTMENTS) != 0
++        );
++
++        convertDisplay(item, type, flags);
++        convertAdventureMode(item, type, flags);
++        convertAttributes(item, type, flags);
++
++        final Object trim = item.tagRemoveGeneric("Trim");
++        if (trim != null) {
++            // note: DFU set does nothing if not map
++            if ((flags & TOOLTIP_FLAG_HIDE_UPGRADES) != 0 && trim instanceof MapType) {
++                ((MapType<String>)trim).setBoolean("show_in_tooltip", false);
++            }
++
++            item.componentSetGeneric("minecraft:trim", trim);
++        }
++
++        if ((flags & TOOLTIP_FLAG_HIDE_ADDITIONAL) != 0) {
++            item.componentSetMap("minecraft:hide_additional_tooltip", type.createEmptyMap());
++        }
++
++        switch (item.id) {
++            case "minecraft:enchanted_book": {
++                convertEnchantments(
++                        item, type, "StoredEnchantments", "minecraft:stored_enchantments",
++                        (flags & TOOLTIP_FLAG_HIDE_ADDITIONAL) != 0
++                );
++                break;
++            }
++            case "minecraft:crossbow": {
++                item.tagRemoveGeneric("Charged");
++                item.tagMigrateNonEmptyListToComponent("ChargedProjectiles", "minecraft:charged_projectiles");
++                break;
++            }
++            case "minecraft:bundle": {
++                item.tagMigrateNonEmptyListToComponent("Items", "minecraft:bundle_contents");
++                break;
++            }
++            case "minecraft:filled_map": {
++                convertMap(item, type);
++                break;
++            }
++            case "minecraft:potion":
++            case "minecraft:splash_potion":
++            case "minecraft:lingering_potion":
++            case "minecraft:tipped_arrow": {
++                convertPotion(item, type);
++                break;
++            }
++            case "minecraft:writable_book": {
++                convertWritableBook(item, type);
++                break;
++            }
++            case "minecraft:written_book": {
++                convertWrittenBook(item, type);
++                break;
++            }
++            case "minecraft:suspicious_stew": {
++                item.tagMigrateToComponent("effects", "minecraft:suspicious_stew_effects");
++                break;
++            }
++            case "minecraft:debug_stick": {
++                item.tagMigrateToComponent("DebugProperty", "minecraft:debug_stick_state");
++                break;
++            }
++            case "minecraft:pufferfish_bucket":
++            case "minecraft:salmon_bucket":
++            case "minecraft:cod_bucket":
++            case "minecraft:tropical_fish_bucket":
++            case "minecraft:axolotl_bucket":
++            case "minecraft:tadpole_bucket": {
++                convertMobBucket(item, type);
++                break;
++            }
++            case "minecraft:goat_horn": {
++                item.tagMigrateToComponent("instrument", "minecraft:instrument");
++                break;
++            }
++            case "minecraft:knowledge_book": {
++                item.tagMigrateToComponent("Recipes", "minecraft:recipes");
++                break;
++            }
++            case "minecraft:compass": {
++                convertCompass(item, type);
++                break;
++            }
++            case "minecraft:firework_rocket": {
++                convertFireworkRocket(item, type);
++                break;
++            }
++            case "minecraft:firework_star": {
++                convertFireworkStar(item, type);
++                break;
++            }
++            case "minecraft:player_head": {
++                convertSukll(item, type);
++                break;
++            }
++        }
++
++        return item.serialize();
++    }
++
++    private ConverterItemStackToDataComponents() {}
++
++    private static final class TransientItemStack {
++
++        private final String id;
++        private final int count;
++
++        private final MapType<String> components;
++        private final MapType<String> tag;
++        private final MapType<String> root;
++
++        public TransientItemStack(final MapType<String> root) {
++            this.id = root.getString("id");
++            this.count = root.getInt("Count");
++
++            final TypeUtil type = root.getTypeUtil();
++
++            this.components = type.createEmptyMap();
++
++            final MapType<String> rootCopy = root.copy();
++
++            final MapType<String> tag = rootCopy.getMap("tag");
++
++            rootCopy.remove("id");
++            rootCopy.remove("Count");
++            rootCopy.remove("tag");
++
++            this.tag = tag == null ? type.createEmptyMap() : tag;
++
++            this.root = rootCopy;
++        }
++
++        public void migrateTagTo(final String tagKey, final MapType<String> dst, final String dstKey) {
++            final Object value = this.tag.getGeneric(tagKey);
++
++            if (value != null) {
++                this.tag.remove(tagKey);
++
++                dst.setGeneric(dstKey, value);
++            }
++        }
++
++        public String tagRemoveString(final String key) {
++            final String ret = this.tag.getString(key);
++
++            this.tag.remove(key);
++
++            return ret;
++        }
++
++        public String tagRemoveString(final String key, final String dfl) {
++            final String ret = this.tag.getString(key, dfl);
++
++            this.tag.remove(key);
++
++            return ret;
++        }
++
++        public ListType tagRemoveListUnchecked(final String key) {
++            final ListType ret = this.tag.getListUnchecked(key);
++
++            this.tag.remove(key);
++
++            return ret;
++        }
++
++        public ListType tagRemoveList(final String key, final ObjectType listType) {
++            final ListType ret = this.tag.getList(key, listType);
++
++            this.tag.remove(key);
++
++            return ret;
++        }
++
++        public MapType<String> tagRemoveMap(final String key) {
++            final MapType<String> ret = this.tag.getMap(key);
++
++            this.tag.remove(key);
++
++            return ret;
++        }
++
++        public boolean tagRemoveBoolean(final String key, final boolean dfl) {
++            final boolean ret = this.tag.getBoolean(key, dfl);
++
++            this.tag.remove(key);
++
++            return ret;
++        }
++
++        public int tagRemoveInt(final String key, final int dfl) {
++            final int ret = this.tag.getInt(key, dfl);
++
++            this.tag.remove(key);
++
++            return ret;
++        }
++
++        public Object tagRemoveGeneric(final String key) {
++            final Object ret = this.tag.getGeneric(key);
++
++            if (ret != null) {
++                this.tag.remove(key);
++                return ret;
++            }
++
++            return ret;
++        }
++
++        public void tagMigrateToComponent(final String tagKey, final String componentKey) {
++            final Object value = this.tag.getGeneric(tagKey);
++            if (value != null) {
++                this.tag.remove(tagKey);
++
++                this.components.setGeneric(componentKey, value);
++            }
++        }
++
++        public void tagMigrateNonEmptyListToComponent(final String tagKey, final String componentKey) {
++            final Object value = this.tag.getGeneric(tagKey);
++            if (value != null) {
++                this.tag.remove(tagKey);
++
++                if (!(value instanceof ListType list) || list.size() > 0) {
++                    this.components.setGeneric(componentKey, value);
++                }
++            }
++        }
++
++        public void tagMigrateToComponent(final String tagKey, final String componentKey, final int defaultComponent) {
++            final int value = this.tag.getInt(tagKey, defaultComponent);
++            this.tag.remove(tagKey);
++
++            if (value != defaultComponent) {
++                this.components.setGeneric(componentKey, value);
++            }
++        }
++
++        public void componentSetBoolean(final String key, final boolean value) {
++            this.components.setBoolean(key, value);
++        }
++
++        public void componentSetString(final String key, final String value) {
++            this.components.setString(key, value);
++        }
++
++        public void componentSetList(final String key, final ListType value) {
++            this.components.setList(key, value);
++        }
++
++        public void componentSetMap(final String key, final MapType<String> value) {
++            this.components.setMap(key, value);
++        }
++
++        public void componentSetGeneric(final String key, final Object value) {
++            this.components.setGeneric(key, value);
++        }
++
++        public MapType<String> serialize() {
++            final MapType<String> ret = this.components.getTypeUtil().createEmptyMap();
++
++            ret.setString("id", this.id);
++            ret.setInt("count", this.count);
++            if (!this.tag.isEmpty()) {
++                this.components.setMap("minecraft:custom_data", this.tag);
++            }
++
++            if (!this.components.isEmpty()) {
++                ret.setMap("components", this.components);
++            }
++
++            // merge root to ret, with entries in ret taking priority
++            if (!this.root.isEmpty()) {
++                for (final String key : this.root.keys()) {
++                    if (ret.hasKey(key)) {
++                        continue;
++                    }
++
++                    ret.setGeneric(key, this.root.getGeneric(key));
++                }
++            }
++
++            return ret;
++        }
++    }
++}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/leveldat/ConverterRemoveFeatureFlag.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/leveldat/ConverterRemoveFeatureFlag.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
@@ -6188,6 +7904,280 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +}
+diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/particle/ConverterParticleToNBT.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/particle/ConverterParticleToNBT.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/particle/ConverterParticleToNBT.java
+@@ -0,0 +0,0 @@
++package ca.spottedleaf.dataconverter.minecraft.converters.particle;
++
++import ca.spottedleaf.dataconverter.types.ListType;
++import ca.spottedleaf.dataconverter.types.MapType;
++import ca.spottedleaf.dataconverter.types.TypeUtil;
++import ca.spottedleaf.dataconverter.types.nbt.NBTMapType;
++import ca.spottedleaf.dataconverter.util.NamespaceUtil;
++import com.mojang.brigadier.StringReader;
++import com.mojang.brigadier.exceptions.CommandSyntaxException;
++import com.mojang.logging.LogUtils;
++import net.minecraft.nbt.CompoundTag;
++import net.minecraft.nbt.TagParser;
++import net.minecraft.util.Mth;
++import org.slf4j.Logger;
++
++public final class ConverterParticleToNBT {
++
++    private static final Logger LOGGER = LogUtils.getLogger();
++
++    private static CompoundTag parseNBT(final String flat) {
++        try {
++            return TagParser.parseTag(flat);
++        } catch (final Exception ex) {
++            LOGGER.warn("Failed to parse nbt: " + flat, ex);
++            return null;
++        }
++    }
++
++    private static void convertItem(final MapType<String> nbt, final String data) {
++        final MapType<String> itemNBT = nbt.getTypeUtil().createEmptyMap();
++        nbt.setMap("item", itemNBT);
++        itemNBT.setInt("Count", 1);
++
++        final int nbtStart = data.indexOf('{');
++        if (nbtStart == -1) {
++            // assume data is item name
++            itemNBT.setString("id", NamespaceUtil.correctNamespace(data));
++            return;
++        }
++        // itemname{tagNBT}
++        itemNBT.setString("id", NamespaceUtil.correctNamespace(data.substring(0, nbtStart)));
++
++        final CompoundTag tag = parseNBT(data.substring(nbtStart));
++        if (tag != null) {
++            // do we need to worry about type conversion?
++            itemNBT.setMap("tag", new NBTMapType(tag));
++        }
++    }
++
++    private static MapType<String> parseProperties(final String input, final TypeUtil type) {
++        final MapType<String> ret = type.createEmptyMap();
++        try {
++            // format: [p1=v1, p2=v2, p3=v3, ...]
++            final StringReader reader = new StringReader(input);
++
++            reader.expect('[');
++            reader.skipWhitespace();
++
++            while (reader.canRead() && reader.peek() != ']') {
++                reader.skipWhitespace();
++
++                final String property = reader.readString();
++
++                reader.skipWhitespace();
++                reader.expect('=');
++                reader.skipWhitespace();
++
++                final String value = reader.readString();
++                ret.setString(property, value);
++
++                reader.skipWhitespace();
++                if (reader.canRead()) {
++                    if (reader.peek() != ',') {
++                        // invalid character or ']'
++                        break;
++                    }
++
++                    // skip ',' and move onto next entry
++                    reader.peek();
++                }
++            }
++
++            reader.expect(']');
++            return ret;
++        } catch (final Exception ex) {
++            LOGGER.warn("Failed to parse block properties: " + input, ex);
++            return null;
++        }
++    }
++
++    private static void convertBlock(final MapType<String> nbt, final String data) {
++        final MapType<String> blockNBT = nbt.getTypeUtil().createEmptyMap();
++        nbt.setMap("block_state", blockNBT);
++
++        final int propertiesStart = data.indexOf('[');
++        if (propertiesStart == -1) {
++            // assume data is id
++            blockNBT.setString("Name", NamespaceUtil.correctNamespace(data));
++            return;
++        }
++        blockNBT.setString("Name", NamespaceUtil.correctNamespace(data.substring(0, propertiesStart)));
++
++        // blockname{properties}
++        final MapType<String> properties = parseProperties(data.substring(propertiesStart), nbt.getTypeUtil());
++        if (properties != null && !properties.isEmpty()) {
++            blockNBT.setMap("Properties", properties);
++        }
++    }
++
++    private static ListType parseFloatVector(final StringReader reader, final TypeUtil type) throws CommandSyntaxException {
++        final float x = reader.readFloat();
++
++        reader.expect(' ');
++        final float y = reader.readFloat();
++
++        reader.expect(' ');
++        final float z = reader.readFloat();
++
++        final ListType ret = type.createEmptyList();
++        ret.addFloat(x);
++        ret.addFloat(y);
++        ret.addFloat(z);
++
++        return ret;
++    }
++
++    private static void convertDust(final MapType<String> nbt, final String data) {
++        try {
++            final StringReader reader = new StringReader(data);
++
++            final ListType color = parseFloatVector(reader, nbt.getTypeUtil());
++
++            reader.expect(' ');
++            final float scale = reader.readFloat();
++
++            nbt.setList("color", color);
++            nbt.setFloat("scale", scale);
++        } catch (final Exception ex) {
++            LOGGER.warn("Failed to parse dust particle: " + data, ex);
++        }
++    }
++
++    private static void convertColorDust(final MapType<String> nbt, final String data) {
++        try {
++            final StringReader reader = new StringReader(data);
++
++            final ListType fromColor = parseFloatVector(reader, nbt.getTypeUtil());
++
++            reader.expect(' ');
++            final float scale = reader.readFloat();
++
++            reader.expect(' ');
++            final ListType toColor = parseFloatVector(reader, nbt.getTypeUtil());
++
++            nbt.setList("from_color", fromColor);
++            nbt.setFloat("scale", scale);
++            nbt.setList("to_color", toColor);
++        } catch (final Exception ex) {
++            LOGGER.warn("Failed to parse color transition dust particle: " + data, ex);
++        }
++    }
++
++    private static void convertSculk(final MapType<String> nbt, final String data) {
++        try {
++            final StringReader reader = new StringReader(data);
++
++            final float roll = reader.readFloat();
++
++            nbt.setFloat("roll", roll);
++        } catch (final Exception ex) {
++            LOGGER.warn("Failed to parse sculk particle: " + data, ex);
++        }
++    }
++
++    private static void convertVibration(final MapType<String> nbt, final String data) {
++        try {
++            final StringReader reader = new StringReader(data);
++
++            final double posX = reader.readDouble();
++
++            reader.expect(' ');
++            final double posY = reader.readDouble();
++
++            reader.expect(' ');
++            final double posZ = reader.readDouble();
++
++            reader.expect(' ');
++            final int arrival = reader.readInt();
++
++            nbt.setInt("arrival_in_ticks", arrival);
++
++            final MapType<String> destination = nbt.getTypeUtil().createEmptyMap();
++            nbt.setMap("destination", destination);
++
++            destination.setString("type", "minecraft:block");
++
++            final ListType pos = nbt.getTypeUtil().createEmptyList();
++            destination.setList("pos", pos);
++
++            pos.addInt(Mth.floor(posX));
++            pos.addInt(Mth.floor(posY));
++            pos.addInt(Mth.floor(posZ));
++        } catch (final Exception ex) {
++            LOGGER.warn("Failed to parse vibration particle: " + data, ex);
++        }
++    }
++
++    private static void convertShriek(final MapType<String> nbt, final String data) {
++        try {
++            final StringReader reader = new StringReader(data);
++
++            final int delay = reader.readInt();
++
++            nbt.setInt("delay", delay);
++        } catch (final Exception ex) {
++            LOGGER.warn("Failed to parse shriek particle: " + data, ex);
++        }
++    }
++
++    public static MapType<String> convert(final String flat, final TypeUtil type) {
++        final String[] split = flat.split(" ", 2);
++        final String name = NamespaceUtil.correctNamespace(split[0]);
++
++        final MapType<String> ret = type.createEmptyMap();
++        ret.setString("type", name);
++
++        if (split.length > 1) {
++            final String data = split[1];
++            switch (name) {
++                case "minecraft:item": {
++                    convertItem(ret, data);
++                    break;
++                }
++                case "minecraft:block":
++                case "minecraft:block_marker":
++                case "minecraft:falling_dust":
++                case "minecraft:dust_pillar": {
++                    convertBlock(ret, data);
++                    break;
++                }
++                case "minecraft:dust": {
++                    convertDust(ret, data);
++                    break;
++                }
++                case "minecraft:dust_color_transition": {
++                    convertColorDust(ret, data);
++                    break;
++                }
++                case "minecraft:sculk_charge": {
++                    convertSculk(ret, data);
++                    break;
++                }
++                case "minecraft:vibration": {
++                    convertVibration(ret, data);
++                    break;
++                }
++                case "minecraft:shriek": {
++                    convertShriek(ret, data);
++                    break;
++                }
++            }
++        }
++
++        return ret;
++    }
++
++    private ConverterParticleToNBT() {}
++}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterAbstractPOIRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterAbstractPOIRename.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
@@ -6769,6 +8759,140 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +}
+diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/DynamicDataType.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/DynamicDataType.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/DynamicDataType.java
+@@ -0,0 +0,0 @@
++package ca.spottedleaf.dataconverter.minecraft.datatypes;
++
++import ca.spottedleaf.dataconverter.converters.DataConverter;
++import ca.spottedleaf.dataconverter.converters.datatypes.DataHook;
++import ca.spottedleaf.dataconverter.converters.datatypes.DataType;
++import ca.spottedleaf.dataconverter.converters.datatypes.DataWalker;
++import ca.spottedleaf.dataconverter.minecraft.MCVersionRegistry;
++import ca.spottedleaf.dataconverter.util.Long2ObjectArraySortedMap;
++import java.util.ArrayList;
++import java.util.List;
++
++public class DynamicDataType extends DataType<Object, Object> {
++
++    public final String name;
++
++    protected final ArrayList<DataConverter<Object, Object>> structureConverters = new ArrayList<>();
++    protected final Long2ObjectArraySortedMap<List<DataWalker<Object>>> structureWalkers = new Long2ObjectArraySortedMap<>();
++    protected final Long2ObjectArraySortedMap<List<DataHook<Object, Object>>> structureHooks = new Long2ObjectArraySortedMap<>();
++
++    public DynamicDataType(final String name) {
++        this.name = name;
++    }
++
++    public void addStructureConverter(final DataConverter<Object, Object> converter) {
++        MCVersionRegistry.checkVersion(converter.getEncodedVersion());
++        this.structureConverters.add(converter);
++        this.structureConverters.sort(DataConverter.LOWEST_VERSION_COMPARATOR);
++    }
++
++    public void addStructureWalker(final int minVersion, final DataWalker<Object> walker) {
++        this.addStructureWalker(minVersion, 0, walker);
++    }
++
++    public void addStructureWalker(final int minVersion, final int versionStep, final DataWalker<Object> walker) {
++        this.structureWalkers.computeIfAbsent(DataConverter.encodeVersions(minVersion, versionStep), (final long keyInMap) -> {
++            return new ArrayList<>();
++        }).add(walker);
++    }
++
++    public void addStructureHook(final int minVersion, final DataHook<Object, Object> hook) {
++        this.addStructureHook(minVersion, 0, hook);
++    }
++
++    public void addStructureHook(final int minVersion, final int versionStep, final DataHook<Object, Object> hook) {
++        this.structureHooks.computeIfAbsent(DataConverter.encodeVersions(minVersion, versionStep), (final long keyInMap) -> {
++            return new ArrayList<>();
++        }).add(hook);
++    }
++
++    @Override
++    public Object convert(Object data, final long fromVersion, final long toVersion) {
++        Object ret = null;
++
++        final List<DataConverter<Object, Object>> converters = this.structureConverters;
++        for (int i = 0, len = converters.size(); i < len; ++i) {
++            final DataConverter<Object, Object> converter = converters.get(i);
++            final long converterVersion = converter.getEncodedVersion();
++
++            if (converterVersion <= fromVersion) {
++                continue;
++            }
++
++            if (converterVersion > toVersion) {
++                break;
++            }
++
++            List<DataHook<Object, Object>> hooks = this.structureHooks.getFloor(converterVersion);
++
++            if (hooks != null) {
++                for (int k = 0, klen = hooks.size(); k < klen; ++k) {
++                    final Object replace = hooks.get(k).preHook(data, fromVersion, toVersion);
++                    if (replace != null) {
++                        ret = data = replace;
++                    }
++                }
++            }
++
++            final Object replace = converter.convert(data, fromVersion, toVersion);
++            if (replace != null) {
++                ret = data = replace;
++            }
++
++            // possibly new data format, update hooks
++            hooks = this.structureHooks.getFloor(toVersion);
++
++            if (hooks != null) {
++                for (int klen = hooks.size(), k = klen - 1; k >= 0; --k) {
++                    final Object postReplace = hooks.get(k).postHook(data, fromVersion, toVersion);
++                    if (postReplace != null) {
++                        ret = data = postReplace;
++                    }
++                }
++            }
++        }
++
++        final List<DataHook<Object, Object>> hooks = this.structureHooks.getFloor(toVersion);
++
++        if (hooks != null) {
++            for (int k = 0, klen = hooks.size(); k < klen; ++k) {
++                final Object replace = hooks.get(k).preHook(data, fromVersion, toVersion);
++                if (replace != null) {
++                    ret = data = replace;
++                }
++            }
++        }
++
++        final List<DataWalker<Object>> walkers = this.structureWalkers.getFloor(toVersion);
++        if (walkers != null) {
++            for (int i = 0, len = walkers.size(); i < len; ++i) {
++                final Object replace = walkers.get(i).walk(data, fromVersion, toVersion);
++                if (replace != null) {
++                    ret = data = replace;
++                }
++            }
++        }
++
++        if (hooks != null) {
++            for (int klen = hooks.size(), k = klen - 1; k >= 0; --k) {
++                final Object postReplace = hooks.get(k).postHook(data, fromVersion, toVersion);
++                if (postReplace != null) {
++                    ret = data = postReplace;
++                }
++            }
++        }
++
++        return ret;
++    }
++}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/IDDataType.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/IDDataType.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
@@ -6791,7 +8915,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public class IDDataType extends MCDataType {
 +
-+    protected final Map<String, Long2ObjectArraySortedMap<List<DataWalker<String>>>> walkersById = new HashMap<>();
++    protected final Map<String, Long2ObjectArraySortedMap<List<DataWalker<MapType<String>>>>> walkersById = new HashMap<>();
 +
 +    public IDDataType(final String name) {
 +        super(name);
@@ -6809,11 +8933,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        });
 +    }
 +
-+    public void addWalker(final int minVersion, final String id, final DataWalker<String> walker) {
++    public void addWalker(final int minVersion, final String id, final DataWalker<MapType<String>> walker) {
 +        this.addWalker(minVersion, 0, id, walker);
 +    }
 +
-+    public void addWalker(final int minVersion, final int versionStep, final String id, final DataWalker<String> walker) {
++    public void addWalker(final int minVersion, final int versionStep, final String id, final DataWalker<MapType<String>> walker) {
 +        this.walkersById.computeIfAbsent(id, (final String keyInMap) -> {
 +            return new Long2ObjectArraySortedMap<>();
 +        }).computeIfAbsent(DataConverter.encodeVersions(minVersion, versionStep), (final long keyInMap) -> {
@@ -6827,18 +8951,18 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +    public void copyWalkers(final int minVersion, final int versionStep, final String fromId, final String toId) {
 +        final long version = DataConverter.encodeVersions(minVersion, versionStep);
-+        final Long2ObjectArraySortedMap<List<DataWalker<String>>> walkersForId = this.walkersById.get(fromId);
++        final Long2ObjectArraySortedMap<List<DataWalker<MapType<String>>>> walkersForId = this.walkersById.get(fromId);
 +        if (walkersForId == null) {
 +            return;
 +        }
 +
-+        final List<DataWalker<String>> nearest = walkersForId.getFloor(version);
++        final List<DataWalker<MapType<String>>> nearest = walkersForId.getFloor(version);
 +
 +        if (nearest == null) {
 +            return;
 +        }
 +
-+        for (final DataWalker<String> walker : nearest) {
++        for (final DataWalker<MapType<String>> walker : nearest) {
 +            this.addWalker(minVersion, versionStep, toId, walker);
 +        }
 +    }
@@ -6904,7 +9028,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +        // run all walkers
 +
-+        final List<DataWalker<String>> walkers = this.structureWalkers.getFloor(toVersion);
++        final List<DataWalker<MapType<String>>> walkers = this.structureWalkers.getFloor(toVersion);
 +        if (walkers != null) {
 +            for (int i = 0, len = walkers.size(); i < len; ++i) {
 +                final MapType<String> replace = walkers.get(i).walk(data, fromVersion, toVersion);
@@ -6914,9 +9038,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            }
 +        }
 +
-+        final Long2ObjectArraySortedMap<List<DataWalker<String>>> walkersByVersion = this.walkersById.get(data.getString("id"));
++        final Long2ObjectArraySortedMap<List<DataWalker<MapType<String>>>> walkersByVersion = this.walkersById.get(data.getString("id"));
 +        if (walkersByVersion != null) {
-+            final List<DataWalker<String>> walkersForId = walkersByVersion.getFloor(toVersion);
++            final List<DataWalker<MapType<String>>> walkersForId = walkersByVersion.getFloor(toVersion);
 +            if (walkersForId != null) {
 +                for (int i = 0, len = walkersForId.size(); i < len; ++i) {
 +                    final MapType<String> replace = walkersForId.get(i).walk(data, fromVersion, toVersion);
@@ -6964,7 +9088,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    public final String name;
 +
 +    protected final ArrayList<DataConverter<MapType<String>, MapType<String>>> structureConverters = new ArrayList<>();
-+    protected final Long2ObjectArraySortedMap<List<DataWalker<String>>> structureWalkers = new Long2ObjectArraySortedMap<>();
++    protected final Long2ObjectArraySortedMap<List<DataWalker<MapType<String>>>> structureWalkers = new Long2ObjectArraySortedMap<>();
 +    protected final Long2ObjectArraySortedMap<List<DataHook<MapType<String>, MapType<String>>>> structureHooks = new Long2ObjectArraySortedMap<>();
 +
 +    public MCDataType(final String name) {
@@ -6977,11 +9101,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        this.structureConverters.sort(DataConverter.LOWEST_VERSION_COMPARATOR);
 +    }
 +
-+    public void addStructureWalker(final int minVersion, final DataWalker<String> walker) {
++    public void addStructureWalker(final int minVersion, final DataWalker<MapType<String>> walker) {
 +        this.addStructureWalker(minVersion, 0, walker);
 +    }
 +
-+    public void addStructureWalker(final int minVersion, final int versionStep, final DataWalker<String> walker) {
++    public void addStructureWalker(final int minVersion, final int versionStep, final DataWalker<MapType<String>> walker) {
 +        this.structureWalkers.computeIfAbsent(DataConverter.encodeVersions(minVersion, versionStep), (final long keyInMap) -> {
 +            return new ArrayList<>();
 +        }).add(walker);
@@ -7054,7 +9178,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            }
 +        }
 +
-+        final List<DataWalker<String>> walkers = this.structureWalkers.getFloor(toVersion);
++        final List<DataWalker<MapType<String>>> walkers = this.structureWalkers.getFloor(toVersion);
 +        if (walkers != null) {
 +            for (int i = 0, len = walkers.size(); i < len; ++i) {
 +                final MapType<String> replace = walkers.get(i).walk(data, fromVersion, toVersion);
@@ -7105,6 +9229,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    public static final IDDataType TILE_ENTITY        = new IDDataType("TileEntity");
 +    public static final IDDataType ITEM_STACK         = new IDDataType("ItemStack");
 +    public static final MCDataType BLOCK_STATE        = new MCDataType("BlockState");
++    public static final MCValueType FLAT_BLOCK_STATE  = new MCValueType("FlatBlockState");
++    public static final MCDataType DATA_COMPONENTS    = new MCDataType("DataComponents");
++    public static final MCDataType VILLAGER_TRADE     = new MCDataType("VillagerTrade");
++    public static final DynamicDataType PARTICLE      = new DynamicDataType("Particle");
 +    public static final MCValueType ENTITY_NAME       = new MCValueType("EntityName");
 +    public static final IDDataType ENTITY             = new IDDataType("Entity");
 +    public static final MCValueType BLOCK_NAME        = new MCValueType("BlockName");
@@ -7125,12 +9253,15 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    public static final MCDataType SAVED_DATA_STRUCTURE_FEATURE_INDICES = new MCDataType("SavedData/StructureFeatureIndices");
 +    public static final MCDataType SAVED_DATA_MAP_DATA                  = new MCDataType("SavedData/MapData");
 +    public static final MCDataType SAVED_DATA_RAIDS                     = new MCDataType("SavedData/Raids");
++    public static final MCDataType SAVED_DATA_COMMAND_STORAGE           = new MCDataType("SavedData/CommandStorage");
++    public static final MCDataType SAVED_DATA_FORCED_CHUNKS             = new MCDataType("SavedData/Chunks");
++    public static final MCDataType SAVED_DATA_MAP_INDEX                 = new MCDataType("SavedData/IdCounts");
++
++    public static final MCValueType DATACONVERTER_CUSTOM_TYPE_COMMAND = new MCValueType("DC_Custom/Command");
 +
 +    static {
 +        try {
 +            registerAll();
-+        } catch (final ThreadDeath thr) {
-+            throw thr;
 +        } catch (final Throwable thr) {
 +            LOGGER.error(LogUtils.FATAL_MARKER, "Failed to register data converters", thr);
 +            throw new RuntimeException(thr);
@@ -7221,7 +9352,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        V1904.register();
 +        V1905.register();
 +        V1906.register();
-+        // V1909 is just adding a simple block entity (jigsaw)
++        V1909.register();
 +        V1911.register();
 +        V1914.register();
 +        V1917.register();
@@ -7353,6 +9484,22 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        V3685.register();
 +        V3689.register();
 +        V3692.register();
++        // V1.20.5
++        V3799.register();
++        V3800.register();
++        V3803.register();
++        V3807.register();
++        V3808.register();
++        V3809.register();
++        V3812.register();
++        V3813.register();
++        V3814.register();
++        V3816.register();
++        V3818.register();
++        V3820.register();
++        V3825.register();
++        V3828.register();
++        V3833.register();
 +    }
 +
 +    private MCTypeRegistry() {}
@@ -7518,7 +9665,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 @@ -0,0 +0,0 @@
 +package ca.spottedleaf.dataconverter.minecraft.util;
 +
++import com.google.gson.JsonElement;
 +import com.google.gson.JsonObject;
++import com.google.gson.JsonParseException;
++import com.google.gson.JsonParser;
++import com.google.gson.JsonPrimitive;
 +import net.minecraft.util.GsonHelper;
 +
 +public final class ComponentUtils {
@@ -7541,6 +9692,53 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        return GsonHelper.toStableString(ret);
 +    }
 +
++    public static String retrieveTranslationString(final String possibleJson) {
++        try {
++            final JsonElement element = JsonParser.parseString(possibleJson);
++
++            if (element instanceof JsonObject object) {
++                final JsonElement translation = object.get("translate");
++                if (translation instanceof JsonPrimitive primitive) {
++                    return primitive.getAsString();
++                }
++            }
++
++            return null;
++        } catch (final Exception ex) {
++            return null;
++        }
++    }
++
++    public static String convertFromLenient(final String input) {
++        if (input == null) {
++            return input;
++        }
++
++        if (input.isEmpty() || input.equals("null")) {
++            return EMPTY;
++        }
++
++        final char firstCharacter = input.charAt(0);
++        final char lastCharacter = input.charAt(input.length() - 1);
++        if ((firstCharacter == '"' && lastCharacter == '"')
++                || (firstCharacter == '{' && lastCharacter == '}')
++                || (firstCharacter == '[' && lastCharacter == ']')) {
++            try {
++                final JsonElement json = JsonParser.parseString(input);
++
++                if (json.isJsonPrimitive()) {
++                    return createPlainTextComponent(json.getAsString());
++                }
++
++                return GsonHelper.toStableString(json);
++            } catch (final JsonParseException ignored) {
++                // fall through to plain text
++            }
++        }
++
++        return createPlainTextComponent(input);
++    }
++
 +    private ComponentUtils() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V100.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V100.java
@@ -7555,6 +9753,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
 +import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
 +import ca.spottedleaf.dataconverter.minecraft.walkers.block_name.DataWalkerBlockNames;
++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerTypePaths;
 +import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists;
 +import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems;
 +import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils;
@@ -7565,10 +9764,22 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V100 {
 +
-+    protected static final int VERSION = MCVersions.V15W32A;
++    private static final int VERSION = MCVersions.V15W32A;
++
++    static void registerEquipment(final int version, final String id) {
++        registerEquipment(version, 0, id);
++    }
++
++    private static final DataWalkerItemLists EQUIPMENT_ITEM_LISTS = new DataWalkerItemLists("ArmorItems", "HandItems");
++    private static final DataWalkerItems EQUIPMENT_ITEMS = new DataWalkerItems("body_armor_item");
++
++    static void registerEquipment(final int version, final int versionStep, final String id) {
++        MCTypeRegistry.ENTITY.addWalker(version, versionStep, id, EQUIPMENT_ITEM_LISTS);
++        MCTypeRegistry.ENTITY.addWalker(version, versionStep, id, EQUIPMENT_ITEMS);
++    }
 +
 +    private static void registerMob(final String id) {
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems"));
++        registerEquipment(VERSION, 0, id);
 +    }
 +
 +    public static void register() {
@@ -7664,18 +9875,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        MCTypeRegistry.ENTITY.addWalker(VERSION, "Villager", (final MapType<String> data, final long fromVersion, final long toVersion) -> {
 +            WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "Inventory", fromVersion, toVersion);
 +
-+            final MapType<String> offers = data.getMap("Offers");
-+            if (offers != null) {
-+                final ListType recipes = offers.getList("Recipes", ObjectType.MAP);
-+                if (recipes != null) {
-+                    for (int i = 0, len = recipes.size(); i < len; ++i) {
-+                        final MapType<String> recipe = recipes.getMap(i);
-+                        WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "buy", fromVersion, toVersion);
-+                        WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "buyB", fromVersion, toVersion);
-+                        WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "sell", fromVersion, toVersion);
-+                    }
-+                }
-+            }
++            WalkerUtils.convertList(MCTypeRegistry.VILLAGER_TRADE, data.getMap("Offers"), "Recipes", fromVersion, toVersion);
 +
 +            WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "ArmorItems", fromVersion, toVersion);
 +            WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "HandItems", fromVersion, toVersion);
@@ -7683,6 +9883,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            return null;
 +        });
 +        registerMob("Shulker");
++        MCTypeRegistry.ENTITY.addWalker(VERSION, "AreaEffectCloud", new DataWalkerTypePaths<>(MCTypeRegistry.PARTICLE, "Particle"));
 +
 +        MCTypeRegistry.STRUCTURE.addStructureWalker(VERSION, (final MapType<String> data, final long fromVersion, final long toVersion) -> {
 +            final ListType entities = data.getList("entities", ObjectType.MAP);
@@ -7706,7 +9907,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    private V100() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V101.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V101.java
 new file mode 100644
@@ -7719,55 +9919,21 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
 +import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
++import ca.spottedleaf.dataconverter.minecraft.util.ComponentUtils;
 +import ca.spottedleaf.dataconverter.types.MapType;
-+import com.google.gson.JsonParseException;
-+import net.minecraft.network.chat.CommonComponents;
-+import net.minecraft.network.chat.Component;
-+import net.minecraft.util.GsonHelper;
-+import net.minecraft.util.datafix.fixes.BlockEntitySignTextStrictJsonFix;
 +
 +public final class V101 {
 +
-+    protected static final int VERSION = MCVersions.V15W32A + 1;
++    private static final int VERSION = MCVersions.V15W32A + 1;
 +
-+    protected static void updateLine(final MapType<String> data, final String path) {
++    private static void updateLine(final MapType<String> data, final String path) {
 +        final String textString = data.getString(path);
-+        if (textString == null || textString.isEmpty() || "null".equals(textString)) {
-+            data.setString(path, Component.Serializer.toJson(CommonComponents.EMPTY));
++
++        if (textString == null) {
 +            return;
 +        }
 +
-+        Component component = null;
-+
-+        if (textString.charAt(0) == '"' && textString.charAt(textString.length() - 1) == '"'
-+                || textString.charAt(0) == '{' && textString.charAt(textString.length() - 1) == '}') {
-+            try {
-+                component = GsonHelper.fromNullableJson(BlockEntitySignTextStrictJsonFix.GSON, textString, Component.class, true);
-+                if (component == null) {
-+                    component = CommonComponents.EMPTY;
-+                }
-+            } catch (final JsonParseException ignored) {}
-+
-+            if (component == null) {
-+                try {
-+                    component = Component.Serializer.fromJson(textString);
-+                } catch (final JsonParseException ignored) {}
-+            }
-+
-+            if (component == null) {
-+                try {
-+                    component = Component.Serializer.fromJsonLenient(textString);
-+                } catch (final JsonParseException ignored) {}
-+            }
-+
-+            if (component == null) {
-+                component = Component.literal(textString);
-+            }
-+        } else {
-+            component = Component.literal(textString);
-+        }
-+
-+        data.setString(path, Component.Serializer.toJson(component));
++        data.setString(path, ComponentUtils.convertFromLenient(textString));
 +    }
 +
 +    public static void register() {
@@ -7785,7 +9951,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    private V101() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V102.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V102.java
 new file mode 100644
@@ -7809,7 +9974,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +    private static final Logger LOGGER = LogUtils.getLogger();
 +
-+    protected static final int VERSION = MCVersions.V15W32A + 2;
++    private static final int VERSION = MCVersions.V15W32A + 2;
 +
 +    public static void register() {
 +        // V102 -> V15W32A + 2
@@ -7878,7 +10043,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    private V102() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1022.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1022.java
 new file mode 100644
@@ -7895,14 +10059,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V1022 {
 +
-+    protected static final int VERSION = MCVersions.V17W06A;
++    private static final int VERSION = MCVersions.V17W06A;
 +
 +    public static void register() {
 +        MCTypeRegistry.PLAYER.addStructureWalker(VERSION, (final MapType<String> data, final long fromVersion, final long toVersion) -> {
-+            final MapType<String> rootVehicle = data.getMap("RootVehicle");
-+            if (rootVehicle != null) {
-+                WalkerUtils.convert(MCTypeRegistry.ENTITY, rootVehicle, "Entity", fromVersion, toVersion);
-+            }
++            WalkerUtils.convert(MCTypeRegistry.ENTITY, data.getMap("RootVehicle"), "Entity", fromVersion, toVersion);
 +
 +            WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "Inventory", fromVersion, toVersion);
 +            WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "EnderItems", fromVersion, toVersion);
@@ -7929,7 +10090,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    private V1022() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V105.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V105.java
 new file mode 100644
@@ -7949,7 +10109,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V105 {
 +
-+    protected static final int VERSION = MCVersions.V15W32C + 1;
++    private static final int VERSION = MCVersions.V15W32C + 1;
 +
 +    public static void register() {
 +        MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:spawn_egg", new DataConverter<>(VERSION) {
@@ -8005,7 +10165,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V106 {
 +
-+    protected static final int VERSION = MCVersions.V15W32C + 2;
++    private static final int VERSION = MCVersions.V15W32C + 2;
 +
 +    public static void register() {
 +        // V106 -> V15W32C + 2
@@ -8074,7 +10234,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    private V106() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V107.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V107.java
 new file mode 100644
@@ -8091,7 +10250,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V107 {
 +
-+    protected static final int VERSION = MCVersions.V15W32C + 3;
++    private static final int VERSION = MCVersions.V15W32C + 3;
 +
 +    public static void register() {
 +        MCTypeRegistry.ENTITY.addConverterForId("Minecart", new DataConverter<>(VERSION) {
@@ -8124,7 +10283,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    private V107() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V108.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V108.java
 new file mode 100644
@@ -8140,14 +10298,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.types.MapType;
 +import com.mojang.logging.LogUtils;
 +import org.slf4j.Logger;
-+
 +import java.util.UUID;
 +
 +public final class V108 {
 +
 +    private static final Logger LOGGER = LogUtils.getLogger();
 +
-+    protected static final int VERSION = MCVersions.V15W32C + 4;
++    private static final int VERSION = MCVersions.V15W32C + 4;
 +
 +    public static void register() {
 +        // Convert String UUID into UUIDMost and UUIDLeast
@@ -8178,7 +10335,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    private V108() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V109.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V109.java
 new file mode 100644
@@ -8192,51 +10348,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
 +import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
 +import ca.spottedleaf.dataconverter.types.MapType;
-+import com.google.common.collect.Sets;
-+import java.util.Set;
 +
 +public final class V109 {
 +
-+    protected static final int VERSION = MCVersions.V15W32C + 5;
-+
-+    // DFU declares this exact field but leaves it unused. Not sure why, legacy conversion system checked if the ID matched.
-+    // I'm going to leave it here unused as well, just in case it's needed in the future.
-+    protected static final Set<String> ENTITIES = Sets.newHashSet(
-+            "ArmorStand",
-+            "Bat",
-+            "Blaze",
-+            "CaveSpider",
-+            "Chicken",
-+            "Cow",
-+            "Creeper",
-+            "EnderDragon",
-+            "Enderman",
-+            "Endermite",
-+            "EntityHorse",
-+            "Ghast",
-+            "Giant",
-+            "Guardian",
-+            "LavaSlime",
-+            "MushroomCow",
-+            "Ozelot",
-+            "Pig",
-+            "PigZombie",
-+            "Rabbit",
-+            "Sheep",
-+            "Shulker",
-+            "Silverfish",
-+            "Skeleton",
-+            "Slime",
-+            "SnowMan",
-+            "Spider",
-+            "Squid",
-+            "Villager",
-+            "VillagerGolem",
-+            "Witch",
-+            "WitherBoss",
-+            "Wolf",
-+            "Zombie"
-+    );
++    private static final int VERSION = MCVersions.V15W32C + 5;
 +
 +    public static void register() {
 +        // Converts health to be in float, and cleans up whatever the hell was going on with HealF and Health...
@@ -8267,7 +10382,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    private V109() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V110.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V110.java
 new file mode 100644
@@ -8286,7 +10400,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V110 {
 +
-+    protected static final int VERSION = MCVersions.V15W32C + 6;
++    private static final int VERSION = MCVersions.V15W32C + 6;
 +
 +    public static void register() {
 +        // Moves the Saddle boolean to be an actual saddle item. Note: The data walker for the SaddleItem exists
@@ -8312,7 +10426,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    private V110() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V111.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V111.java
 new file mode 100644
@@ -8329,11 +10442,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V111 {
 +
-+    protected static final int VERSION = MCVersions.V15W33B;
++    private static final int VERSION = MCVersions.V15W33B;
 +
 +    public static void register() {
-+        MCTypeRegistry.ENTITY.addConverterForId("Painting", new EntityRotationFix("Painting"));
-+        MCTypeRegistry.ENTITY.addConverterForId("ItemFrame", new EntityRotationFix("ItemFrame"));
++        final EntityRotationFix rotationFix = new EntityRotationFix(VERSION);
++        MCTypeRegistry.ENTITY.addConverterForId("Painting", rotationFix);
++        MCTypeRegistry.ENTITY.addConverterForId("ItemFrame", rotationFix);
 +    }
 +
 +    private V111() {}
@@ -8347,11 +10461,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                {1, 0, 0}
 +        };
 +
-+        protected final String id;
-+
-+        public EntityRotationFix(final String id) {
-+            super(VERSION);
-+            this.id = id;
++        public EntityRotationFix(final int version) {
++            super(version);
 +        }
 +
 +        @Override
@@ -8385,7 +10496,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            return null;
 +        }
 +    }
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1125.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1125.java
 new file mode 100644
@@ -8407,8 +10517,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V1125 {
 +
-+    protected static final int VERSION = MCVersions.V17W15A;
-+    protected static final int BED_BLOCK_ID = 416;
++    private static final int VERSION = MCVersions.V17W15A;
++    private static final int BED_BLOCK_ID = 416;
 +
 +    public static void register() {
 +        MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) {
@@ -8493,7 +10603,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    private V1125() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V113.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V113.java
 new file mode 100644
@@ -8512,9 +10621,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V113 {
 +
-+    protected static final int VERSION = MCVersions.V15W33C + 1;
++    private static final int VERSION = MCVersions.V15W33C + 1;
 +
-+    protected static void checkList(final MapType<String> data, final String id, final int requiredLength) {
++    private static void checkList(final MapType<String> data, final String id, final int requiredLength) {
 +        final ListType list = data.getList(id, ObjectType.FLOAT);
 +        if (list != null && list.size() == requiredLength) {
 +            for (int i = 0; i < requiredLength; ++i) {
@@ -8540,7 +10649,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    private V113() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1344.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1344.java
 new file mode 100644
@@ -8558,7 +10666,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V1344 {
 +
-+    protected static final int VERSION = MCVersions.V1_12_2 + 1;
++    private static final int VERSION = MCVersions.V1_12_2 + 1;
 +
 +    private static final Int2ObjectOpenHashMap<String> BUTTON_ID_TO_NAME = new Int2ObjectOpenHashMap<>();
 +    static {
@@ -8723,7 +10831,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    private V1344() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V135.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V135.java
 new file mode 100644
@@ -8745,7 +10852,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V135 {
 +
-+    protected static final int VERSION = MCVersions.V15W40B + 1;
++    private static final int VERSION = MCVersions.V15W40B + 1;
 +
 +    public static void register() {
 +        // In this update they changed the "Riding" value to be "Passengers", which is now a list. So it added
@@ -8790,7 +10897,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    private V135() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V143.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V143.java
 new file mode 100644
@@ -8805,7 +10911,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V143 {
 +
-+    protected static final int VERSION = MCVersions.V15W44B;
++    private static final int VERSION = MCVersions.V15W44B;
 +
 +    public static void register() {
 +        ConverterAbstractEntityRename.register(VERSION, (final String input) -> {
@@ -8814,7 +10920,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    private V143() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1446.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1446.java
 new file mode 100644
@@ -8831,7 +10936,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V1446 {
 +
-+    protected static final int VERSION = MCVersions.V17W43B + 1;
++    private static final int VERSION = MCVersions.V17W43B + 1;
 +
 +    public static void register() {
 +        MCTypeRegistry.OPTIONS.addStructureConverter(new DataConverter<>(VERSION) {
@@ -8856,7 +10961,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    private V1446() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1450.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1450.java
 new file mode 100644
@@ -8874,7 +10978,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V1450 {
 +
-+    protected static final int VERSION = MCVersions.V17W46A + 1;
++    private static final int VERSION = MCVersions.V17W46A + 1;
 +
 +    public static void register() {
 +        MCTypeRegistry.BLOCK_STATE.addStructureConverter(new DataConverter<>(VERSION) {
@@ -8887,7 +10991,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    private V1450() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1451.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1451.java
 new file mode 100644
@@ -8929,7 +11032,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V1451 {
 +
-+    protected static final int VERSION = MCVersions.V17W47A;
++    private static final int VERSION = MCVersions.V17W47A;
 +
 +    public static String packWithDot(final String string) {
 +        final ResourceLocation resourceLocation = ResourceLocation.tryParse(string);
@@ -9010,7 +11113,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +        MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:potion", new DataWalkerItems("Potion"));
 +        MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:arrow", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "inBlockState"));
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:enderman", new DataWalkerItemLists("ArmorItems", "HandItems"));
++        V100.registerEquipment(VERSION, 3, "minecraft:enderman");
 +        MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:enderman", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "carriedBlockState"));
 +        MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:falling_block", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "BlockState"));
 +        MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:falling_block", new DataWalkerTileEntities("TileEntityData"));
@@ -9423,9 +11526,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V1456 {
 +
-+    protected static final int VERSION = MCVersions.V17W49B + 1;
++    private static final int VERSION = MCVersions.V17W49B + 1;
 +
-+    protected static byte direction2dTo3d(final byte old) {
++    private static byte direction2dTo3d(final byte old) {
 +        switch (old) {
 +            case 0:
 +                return 3;
@@ -9450,7 +11553,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    private V1456() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1458.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1458.java
 new file mode 100644
@@ -9468,7 +11570,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V1458 {
 +
-+    protected static final int VERSION = MCVersions.V17W50A + 1;
++    private static final int VERSION = MCVersions.V17W50A + 1;
 +
 +    public static MapType<String> updateCustomName(final MapType<String> data) {
 +        final String customName = data.getString("CustomName", "");
@@ -9518,13 +11620,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                final String name = display.getString("Name");
 +                if (name != null) {
 +                    display.setString("Name", ComponentUtils.createPlainTextComponent(name));
-+                } else {
++                } /* In 1.20.5, Mojang removed this branch (ItemCustomNameToComponentFix) */ /*else {
 +                    final String localisedName = display.getString("LocName");
 +                    if (localisedName != null) {
 +                        display.setString("Name", ComponentUtils.createTranslatableComponent(localisedName));
 +                        display.remove("LocName");
 +                    }
-+                }
++                }*/
 +
 +                return null;
 +            }
@@ -9544,7 +11646,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    private V1458() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1460.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1460.java
 new file mode 100644
@@ -9573,17 +11674,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        MOTIVE_REMAP.put("donkeykong", "donkey_kong");
 +        MOTIVE_REMAP.put("burningskull", "burning_skull");
 +        MOTIVE_REMAP.put("skullandroses", "skull_and_roses");
-+    };
-+
-+    protected static final int VERSION = MCVersions.V18W01A + 1;
-+
-+    private static void registerMob(final String id) {
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems"));
 +    }
 +
-+    private static void registerThrowableProjectile(final String id) {
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerBlockNames("inTile"));
-+    }
++    private static final int VERSION = MCVersions.V18W01A + 1;
 +
 +    public static void register() {
 +        MCTypeRegistry.ENTITY.addConverterForId("minecraft:painting", new DataConverter<>(VERSION) {
@@ -9603,7 +11696,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    private V1460() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1466.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1466.java
 new file mode 100644
@@ -9624,9 +11716,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V1466 {
 +
-+    protected static final int VERSION = MCVersions.V18W06A;
++    private static final int VERSION = MCVersions.V18W06A;
 +
-+    protected static short packOffsetCoordinates(final int x, final int y, final int z) {
++    private static short packOffsetCoordinates(final int x, final int y, final int z) {
 +        return (short)((x & 15) | ((y & 15) << 4) | ((z & 15) << 8));
 +    }
 +
@@ -9752,7 +11844,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    private V1466() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V147.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V147.java
 new file mode 100644
@@ -9769,7 +11860,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V147 {
 +
-+    protected static final int VERSION = MCVersions.V15W46A + 1;
++    private static final int VERSION = MCVersions.V15W46A + 1;
 +
 +    public static void register() {
 +        MCTypeRegistry.ENTITY.addConverterForId("ArmorStand", new DataConverter<>(VERSION) {
@@ -9785,7 +11876,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    private V147() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1470.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1470.java
 new file mode 100644
@@ -9798,15 +11888,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
 +import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
 +import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerTypePaths;
-+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists;
 +import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems;
 +
 +public final class V1470 {
 +
-+    protected static final int VERSION = MCVersions.V18W08A;
++    private static final int VERSION = MCVersions.V18W08A;
 +
 +    private static void registerMob(final String id) {
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems"));
++        V100.registerEquipment(VERSION, id);
 +    }
 +
 +    public static void register() {
@@ -9824,7 +11913,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    private V1470() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1474.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1474.java
 new file mode 100644
@@ -9843,7 +11931,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V1474 {
 +
-+    protected static final int VERSION = MCVersions.V18W10B;
++    private static final int VERSION = MCVersions.V18W10B;
 +
 +    public static void register() {
 +        MCTypeRegistry.ENTITY.addConverterForId("minecraft:shulker", new DataConverter<>(VERSION) {
@@ -9862,11 +11950,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        ConverterAbstractItemRename.register(VERSION, (final String old) -> {
 +            return "minecraft:purple_shulker_box".equals(old) ? "minecraft:shulker_box" : null;
 +        });
-+
 +    }
 +
 +    private V1474() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1475.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1475.java
 new file mode 100644
@@ -9879,21 +11965,22 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
 +import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename;
 +import com.google.common.collect.ImmutableMap;
++import java.util.HashMap;
 +
 +public final class V1475 {
 +
-+    protected static final int VERSION = MCVersions.V18W10B + 1;
++    private static final int VERSION = MCVersions.V18W10B + 1;
 +
 +    public static void register() {
-+        ConverterAbstractBlockRename.register(VERSION,
++        ConverterAbstractBlockRename.register(VERSION, new HashMap<>(
 +                ImmutableMap.of(
 +                        "minecraft:flowing_water", "minecraft:water",
 +                        "minecraft:flowing_lava", "minecraft:lava"
-+                )::get);
++                )
++        )::get);
 +    }
 +
 +    private V1475() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1480.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1480.java
 new file mode 100644
@@ -9912,7 +11999,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V1480 {
 +
-+    protected static final int VERSION = MCVersions.V18W14A + 1;
++    private static final int VERSION = MCVersions.V18W14A + 1;
 +
 +    public static final Map<String, String> RENAMED_IDS = new HashMap<>(
 +            ImmutableMap.<String, String>builder()
@@ -9958,30 +12045,29 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterAbstractEntityRename;
 +import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename;
 +import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
-+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists;
 +import com.google.common.collect.ImmutableMap;
++import java.util.HashMap;
 +
 +public final class V1483 {
 +
-+    protected static final int VERSION = MCVersions.V18W16A;
-+
-+    private static void registerMob(final String id) {
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems"));
-+    }
++    private static final int VERSION = MCVersions.V18W16A;
 +
 +    public static void register() {
-+        ConverterAbstractEntityRename.register(VERSION, ImmutableMap.of(
-+                "minecraft:puffer_fish", "minecraft:pufferfish"
++        ConverterAbstractEntityRename.register(VERSION, new HashMap<>(
++                ImmutableMap.of(
++                        "minecraft:puffer_fish", "minecraft:pufferfish"
++                )
 +        )::get);
-+        ConverterAbstractItemRename.register(VERSION, ImmutableMap.of(
-+                "minecraft:puffer_fish_spawn_egg", "minecraft:pufferfish_spawn_egg"
++        ConverterAbstractItemRename.register(VERSION, new HashMap<>(
++                ImmutableMap.of(
++                        "minecraft:puffer_fish_spawn_egg", "minecraft:pufferfish_spawn_egg"
++                )
 +        )::get);
 +
 +        MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:puffer_fish", "minecraft:pufferfish");
 +    }
 +
 +    private V1483() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1484.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1484.java
 new file mode 100644
@@ -9998,16 +12084,19 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
 +import ca.spottedleaf.dataconverter.types.MapType;
 +import com.google.common.collect.ImmutableMap;
++import java.util.HashMap;
 +import java.util.Map;
 +
 +public final class V1484 {
 +
-+    protected static final int VERSION = MCVersions.V18W19A;
++    private static final int VERSION = MCVersions.V18W19A;
 +
 +    public static void register() {
-+        final Map<String, String> renamed = ImmutableMap.of(
-+                "minecraft:sea_grass", "minecraft:seagrass",
-+                "minecraft:tall_sea_grass", "minecraft:tall_seagrass"
++        final Map<String, String> renamed = new HashMap<>(
++                ImmutableMap.of(
++                        "minecraft:sea_grass", "minecraft:seagrass",
++                        "minecraft:tall_sea_grass", "minecraft:tall_seagrass"
++                )
 +        );
 +
 +        ConverterAbstractItemRename.register(VERSION, renamed::get);
@@ -10060,7 +12149,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    private V1484() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1486.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1486.java
 new file mode 100644
@@ -10081,11 +12169,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V1486 {
 +
-+    protected static final int VERSION = MCVersions.V18W19B + 1;
-+
-+    private static void registerMob(final String id) {
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems"));
-+    }
++    private static final int VERSION = MCVersions.V18W19B + 1;
 +
 +    public static final Map<String, String> RENAMED_ENTITY_IDS = new HashMap<>(
 +            ImmutableMap.<String, String>builder()
@@ -10110,7 +12194,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    private V1486() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1487.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1487.java
 new file mode 100644
@@ -10124,16 +12207,19 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename;
 +import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename;
 +import com.google.common.collect.ImmutableMap;
++import java.util.HashMap;
 +import java.util.Map;
 +
 +public final class V1487 {
 +
-+    protected static final int VERSION = MCVersions.V18W19B + 2;
++    private static final int VERSION = MCVersions.V18W19B + 2;
 +
 +    public static void register() {
-+        final Map<String, String> remap = ImmutableMap.of(
-+                "minecraft:prismarine_bricks_slab", "minecraft:prismarine_brick_slab",
-+                "minecraft:prismarine_bricks_stairs", "minecraft:prismarine_brick_stairs"
++        final Map<String, String> remap = new HashMap<>(
++                ImmutableMap.of(
++                        "minecraft:prismarine_bricks_slab", "minecraft:prismarine_brick_slab",
++                        "minecraft:prismarine_bricks_stairs", "minecraft:prismarine_brick_stairs"
++                )
 +        );
 +
 +        ConverterAbstractItemRename.register(VERSION, remap::get);
@@ -10141,7 +12227,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    private V1487() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1488.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1488.java
 new file mode 100644
@@ -10161,21 +12246,27 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.types.ObjectType;
 +import com.google.common.collect.ImmutableMap;
 +
++import java.util.HashMap;
++
 +public final class V1488 {
 +
-+    protected static final int VERSION = MCVersions.V18W19B + 3;
++    private static final int VERSION = MCVersions.V18W19B + 3;
 +
-+    protected static boolean isIglooPiece(final MapType<String> piece) {
++    private static boolean isIglooPiece(final MapType<String> piece) {
 +        return "Iglu".equals(piece.getString("id"));
 +    }
 +
 +    public static void register() {
-+        ConverterAbstractBlockRename.register(VERSION, ImmutableMap.of(
-+                "minecraft:kelp_top", "minecraft:kelp",
-+                "minecraft:kelp", "minecraft:kelp_plant"
++        ConverterAbstractBlockRename.register(VERSION, new HashMap<>(
++                ImmutableMap.of(
++                        "minecraft:kelp_top", "minecraft:kelp",
++                        "minecraft:kelp", "minecraft:kelp_plant"
++                )
 +        )::get);
-+        ConverterAbstractItemRename.register(VERSION, ImmutableMap.of(
-+                "minecraft:kelp_top", "minecraft:kelp"
++        ConverterAbstractItemRename.register(VERSION, new HashMap<>(
++                ImmutableMap.of(
++                        "minecraft:kelp_top", "minecraft:kelp"
++                )
 +        )::get);
 +
 +        // Don't ask me why in V1458 they wrote the converter to NOT do command blocks and THEN in THIS version
@@ -10235,7 +12326,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    private V1488() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1490.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1490.java
 new file mode 100644
@@ -10250,23 +12340,28 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename;
 +import com.google.common.collect.ImmutableMap;
 +
++import java.util.HashMap;
++
 +public final class V1490 {
 +
-+    protected static final int VERSION = MCVersions.V18W20A + 1;
++    private static final int VERSION = MCVersions.V18W20A + 1;
 +
 +    public static void register() {
-+        ConverterAbstractBlockRename.register(VERSION, ImmutableMap.of(
-+                "minecraft:melon_block", "minecraft:melon"
++        ConverterAbstractBlockRename.register(VERSION, new HashMap<>(
++                ImmutableMap.of(
++                        "minecraft:melon_block", "minecraft:melon"
++                )
 +        )::get);
-+        ConverterAbstractItemRename.register(VERSION, ImmutableMap.of(
-+                "minecraft:melon_block", "minecraft:melon",
-+                "minecraft:melon", "minecraft:melon_slice",
-+                "minecraft:speckled_melon", "minecraft:glistering_melon_slice"
++        ConverterAbstractItemRename.register(VERSION, new HashMap<>(
++                ImmutableMap.of(
++                        "minecraft:melon_block", "minecraft:melon",
++                        "minecraft:melon", "minecraft:melon_slice",
++                        "minecraft:speckled_melon", "minecraft:glistering_melon_slice"
++                )
 +        )::get);
 +    }
 +
 +    private V1490() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1492.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1492.java
 new file mode 100644
@@ -10391,7 +12486,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +            .build();
 +
-+    protected static final int VERSION = MCVersions.V18W20B + 1;
++    private static final int VERSION = MCVersions.V18W20B + 1;
 +
 +    public static void register() {
 +        MCTypeRegistry.STRUCTURE_FEATURE.addStructureConverter(new DataConverter<>(VERSION) {
@@ -10424,7 +12519,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    private V1492() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1494.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1494.java
 new file mode 100644
@@ -10444,7 +12538,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V1494 {
 +
-+    protected static final int VERSION = MCVersions.V18W20C + 1;
++    private static final int VERSION = MCVersions.V18W20C + 1;
 +
 +    private static final Int2ObjectOpenHashMap<String> ENCH_ID_TO_NAME = new Int2ObjectOpenHashMap<>();
 +    static {
@@ -10519,7 +12613,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    private V1494() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1496.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1496.java
 new file mode 100644
@@ -10548,9 +12641,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V1496 {
 +
-+    private V1496() {}
-+
-+    protected static final int VERSION = MCVersions.V18W21B;
++    private static final int VERSION = MCVersions.V18W21B;
 +
 +    private static final int[][] DIRECTIONS = new int[][] {
 +            new int[] {-1, 0, 0},
@@ -10754,6 +12845,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        return ret;
 +    }
 +
++    private V1496() {}
++
 +    public abstract static class Section {
 +        protected final ListType palette;
 +        protected final int sectionY;
@@ -10912,9 +13005,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V1500 {
 +
-+    protected static final int VERSION = MCVersions.V18W22C + 1;
-+
-+    private V1500() {}
++    private static final int VERSION = MCVersions.V18W22C + 1;
 +
 +    public static void register() {
 +        MCTypeRegistry.TILE_ENTITY.addConverterForId("DUMMY", new DataConverter<>(VERSION) {
@@ -10926,6 +13017,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        });
 +    }
 +
++    private V1500() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1501.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1501.java
 new file mode 100644
@@ -10943,7 +13035,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V1501 {
 +
-+    protected static final int VERSION = MCVersions.V1_13_PRE1;
++    private static final int VERSION = MCVersions.V1_13_PRE1;
 +
 +    private static final Map<String, String> RENAMES = new HashMap<>(
 +            ImmutableMap.<String, String>builder()
@@ -11005,11 +13097,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                    .build()
 +    );
 +
-+    private V1501() {}
-+
 +    public static void register() {
 +        ConverterAbstractAdvancementsRename.register(VERSION, RENAMES::get);
 +    }
++
++    private V1501() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1502.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1502.java
 new file mode 100644
@@ -11027,7 +13119,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V1502 {
 +
-+    protected static final int VERSION = MCVersions.V1_13_PRE2;
++    private static final int VERSION = MCVersions.V1_13_PRE2;
 +
 +    private static final Map<String, String> RECIPES_UPDATES = new HashMap<>(
 +            ImmutableMap.<String, String>builder()
@@ -11088,11 +13180,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                    .build()
 +    );
 +
-+    private V1502() {}
-+
 +    public static void register() {
 +        ConverterAbstractRecipeRename.register(VERSION, RECIPES_UPDATES::get);
 +    }
++
++    private V1502() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1506.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1506.java
 new file mode 100644
@@ -11131,7 +13223,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V1506 {
 +
-+    protected static final int VERSION = MCVersions.V1_13_PRE4 + 2;
++    private static final int VERSION = MCVersions.V1_13_PRE4 + 2;
 +
 +    static final Map<String, String> MAP = new HashMap<>();
 +    static {
@@ -11210,8 +13302,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        MAP.put("167", "minecraft:modified_badlands_plateau");
 +    }
 +
-+    private V1506() {}
-+
 +    public static void register() {
 +        MCTypeRegistry.LEVEL.addStructureConverter(new DataConverter<>(VERSION) {
 +            @Override
@@ -11318,6 +13408,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +        return ret;
 +    }
++
++    private V1506() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1510.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1510.java
 new file mode 100644
@@ -11397,9 +13489,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                    .build()
 +    );
 +
-+    protected static final int VERSION = MCVersions.V1_13_PRE4 + 6;
-+
-+    private V1510() {}
++    private static final int VERSION = MCVersions.V1_13_PRE4 + 6;
 +
 +    public static void register() {
 +        ConverterAbstractBlockRename.register(VERSION, RENAMED_BLOCKS::get);
@@ -11435,6 +13525,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:eye_of_ender_signal", "minecraft:eye_of_ender");
 +        MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:fireworks_rocket", "minecraft:firework_rocket");
 +    }
++
++    private V1510() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1514.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1514.java
 new file mode 100644
@@ -11452,9 +13544,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V1514 {
 +
-+    protected static final int VERSION = MCVersions.V1_13_PRE7 + 1;
-+
-+    private V1514() {}
++    private static final int VERSION = MCVersions.V1_13_PRE7 + 1;
 +
 +    public static void register() {
 +        MCTypeRegistry.OBJECTIVE.addStructureConverter(new DataConverter<>(VERSION) {
@@ -11509,6 +13599,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            }
 +        });
 +    }
++
++    private V1514() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1515.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1515.java
 new file mode 100644
@@ -11526,7 +13618,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V1515 {
 +
-+    protected static final int VERSION = MCVersions.V1_13_PRE7 + 2;
++    private static final int VERSION = MCVersions.V1_13_PRE7 + 2;
 +
 +    public static final Map<String, String> RENAMED_BLOCK_IDS = new HashMap<>(
 +            ImmutableMap.<String, String>builder()
@@ -11538,11 +13630,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                    .build()
 +    );
 +
-+    private V1515() {}
-+
 +    public static void register() {
 +        ConverterAbstractBlockRename.register(VERSION, RENAMED_BLOCK_IDS::get);
 +    }
++
++    private V1515() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1624.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1624.java
 new file mode 100644
@@ -11566,9 +13658,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +    private static final Logger LOGGER = LogUtils.getLogger();
 +
-+    protected static final int VERSION = MCVersions.V18W32A + 1;
-+
-+    private V1624() {}
++    private static final int VERSION = MCVersions.V18W32A + 1;
 +
 +    public static void register() {
 +        MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) {
@@ -11632,6 +13722,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        });
 +    }
 +
++    private V1624() {}
++
 +    public static final class TrappedChestSection extends V1496.Section {
 +
 +        private IntOpenHashSet chestIds;
@@ -11671,19 +13763,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
 +import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
++import ca.spottedleaf.dataconverter.minecraft.util.ComponentUtils;
 +import ca.spottedleaf.dataconverter.types.ObjectType;
 +import ca.spottedleaf.dataconverter.types.ListType;
 +import ca.spottedleaf.dataconverter.types.MapType;
-+import com.google.gson.JsonParseException;
-+import net.minecraft.network.chat.CommonComponents;
-+import net.minecraft.network.chat.Component;
-+import net.minecraft.util.GsonHelper;
-+import net.minecraft.util.datafix.fixes.BlockEntitySignTextStrictJsonFix;
-+import org.apache.commons.lang3.StringUtils;
 +
 +public final class V165 {
 +
-+    protected static final int VERSION = MCVersions.V1_9_PRE2;
++    private static final int VERSION = MCVersions.V1_9_PRE2;
 +
 +    public static void register() {
 +        MCTypeRegistry.ITEM_STACK.addStructureConverter(new DataConverter<>(VERSION) {
@@ -11701,40 +13788,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +                for (int i = 0, len = pages.size(); i < len; ++i) {
 +                    final String page = pages.getString(i);
-+                    Component component = null;
 +
-+                    if (!"null".equals(page) && !StringUtils.isEmpty(page)) {
-+                        if (page.charAt(0) == '"' && page.charAt(page.length() - 1) == '"' || page.charAt(0) == '{' && page.charAt(page.length() - 1) == '}') {
-+                            try {
-+                                component = GsonHelper.fromNullableJson(BlockEntitySignTextStrictJsonFix.GSON, page, Component.class, true);
-+                                if (component == null) {
-+                                    component = CommonComponents.EMPTY;
-+                                }
-+                            } catch (final JsonParseException ignored) {}
-+
-+                            if (component == null) {
-+                                try {
-+                                    component = Component.Serializer.fromJson(page);
-+                                } catch (final JsonParseException ignored) {}
-+                            }
-+
-+                            if (component == null) {
-+                                try {
-+                                    component = Component.Serializer.fromJsonLenient(page);
-+                                } catch (JsonParseException ignored) {}
-+                            }
-+
-+                            if (component == null) {
-+                                component = Component.literal(page);
-+                            }
-+                        } else {
-+                            component = Component.literal(page);
-+                        }
-+                    } else {
-+                        component = CommonComponents.EMPTY;
-+                    }
-+
-+                    pages.setString(i, Component.Serializer.toJson(component));
++                    pages.setString(i, ComponentUtils.convertFromLenient(page));
 +                }
 +
 +                return null;
@@ -11743,7 +13798,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    private V165() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1800.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1800.java
 new file mode 100644
@@ -11763,7 +13817,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V1800 {
 +
-+    protected static final int VERSION = MCVersions.V1_13_2 + 169;
++    private static final int VERSION = MCVersions.V1_13_2 + 169;
 +
 +    public static final Map<String, String> RENAMED_ITEM_IDS = new HashMap<>(
 +            ImmutableMap.<String, String>builder()
@@ -11773,19 +13827,19 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                    .build()
 +    );
 +
-+    private V1800() {}
-+
 +    private static void registerMob(final String id) {
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems"));
++        V100.registerEquipment(VERSION, id);
 +    }
 +
 +    public static void register() {
 +        ConverterAbstractItemRename.register(VERSION, RENAMED_ITEM_IDS::get);
 +
 +        registerMob("minecraft:panda");
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:pillager", new DataWalkerItemLists("Inventory", "ArmorItems", "HandItems"));
++        MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:pillager", new DataWalkerItemLists("Inventory"));
++        V100.registerEquipment(VERSION, "minecraft:pillager");
 +    }
 +
++    private V1800() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1801.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1801.java
 new file mode 100644
@@ -11796,23 +13850,20 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
-+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
-+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists;
 +
 +public final class V1801 {
 +
-+    protected static final int VERSION = MCVersions.V1_13_2 + 170;
-+
-+    private V1801() {}
++    private static final int VERSION = MCVersions.V1_13_2 + 170;
 +
 +    private static void registerMob(final String id) {
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems"));
++        V100.registerEquipment(VERSION, id);
 +    }
 +
 +    public static void register() {
 +        registerMob("minecraft:illager_beast");
 +    }
 +
++    private V1801() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1802.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1802.java
 new file mode 100644
@@ -11826,24 +13877,27 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename;
 +import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename;
 +import com.google.common.collect.ImmutableMap;
++import java.util.HashMap;
 +
 +public final class V1802 {
 +
-+    protected static final int VERSION = MCVersions.V1_13_2 + 171;
-+
-+    private V1802() {}
++    private static final int VERSION = MCVersions.V1_13_2 + 171;
 +
 +    public static void register() {
-+        ConverterAbstractBlockRename.register(VERSION, ImmutableMap.of(
-+                "minecraft:stone_slab", "minecraft:smooth_stone_slab",
-+                "minecraft:sign", "minecraft:oak_sign", "minecraft:wall_sign", "minecraft:oak_wall_sign"
++        ConverterAbstractBlockRename.register(VERSION, new HashMap<>(
++                ImmutableMap.of(
++                        "minecraft:stone_slab", "minecraft:smooth_stone_slab",
++                        "minecraft:sign", "minecraft:oak_sign", "minecraft:wall_sign", "minecraft:oak_wall_sign"
++                )
 +        )::get);
-+        ConverterAbstractItemRename.register(VERSION, ImmutableMap.of(
++        ConverterAbstractItemRename.register(VERSION, new HashMap<>(ImmutableMap.of(
 +                "minecraft:stone_slab", "minecraft:smooth_stone_slab",
 +                "minecraft:sign", "minecraft:oak_sign"
++        )
 +        )::get);
 +    }
 +
++    private V1802() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1803.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1803.java
 new file mode 100644
@@ -11863,9 +13917,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V1803 {
 +
-+    protected static final int VERSION = MCVersions.V1_13_2 + 172;
-+
-+    private V1803() {}
++    private static final int VERSION = MCVersions.V1_13_2 + 172;
 +
 +    public static void register() {
 +        MCTypeRegistry.ITEM_STACK.addStructureConverter(new DataConverter<>(VERSION) {
@@ -11897,6 +13949,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        });
 +    }
 +
++    private V1803() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1904.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1904.java
 new file mode 100644
@@ -11914,12 +13967,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V1904 {
 +
-+    protected static final int VERSION = MCVersions.V18W43C + 1;
-+
-+    private V1904() {}
++    private static final int VERSION = MCVersions.V18W43C + 1;
 +
 +    private static void registerMob(final String id) {
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems"));
++        V100.registerEquipment(VERSION, id);
 +    }
 +
 +    public static void register() {
@@ -11946,7 +13997,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        registerMob("minecraft:cat");
 +    }
 +
-+
++    private V1904() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1905.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1905.java
 new file mode 100644
@@ -11963,9 +14014,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V1905 {
 +
-+    protected static final int VERSION = MCVersions.V18W43C + 2;
-+
-+    private V1905() {}
++    private static final int VERSION = MCVersions.V18W43C + 2;
 +
 +    public static void register() {
 +        MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) {
@@ -11988,6 +14037,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        });
 +    }
 +
++    private V1905() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1906.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1906.java
 new file mode 100644
@@ -12004,9 +14054,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V1906 {
 +
-+    protected static final int VERSION = MCVersions.V18W43C + 3;
-+
-+    private V1906() {}
++    private static final int VERSION = MCVersions.V18W43C + 3;
 +
 +    public static void register() {
 +        MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:barrel", new DataWalkerItemLists("Items"));
@@ -12015,6 +14063,29 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:lectern", new DataWalkerItems("Book"));
 +    }
 +
++    private V1906() {}
++}
+diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1909.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1909.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1909.java
+@@ -0,0 +0,0 @@
++package ca.spottedleaf.dataconverter.minecraft.versions;
++
++import ca.spottedleaf.dataconverter.minecraft.MCVersions;
++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerTypePaths;
++
++public final class V1909 {
++
++    private static final int VERSION = MCVersions.V18W45A + 1;
++
++    public static void register() {
++        MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:jigsaw", new DataWalkerTypePaths<>(MCTypeRegistry.FLAT_BLOCK_STATE, "final_state"));
++    }
++
++    private V1909() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1911.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1911.java
 new file mode 100644
@@ -12034,7 +14105,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V1911 {
 +
-+    protected static final int VERSION = MCVersions.V18W46A + 1;
++    private static final int VERSION = MCVersions.V18W46A + 1;
 +
 +    private static final Map<String, String> CHUNK_STATUS_REMAP = new HashMap<>(
 +            ImmutableMap.<String, String>builder()
@@ -12051,9 +14122,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                    .build()
 +    );
 +
-+
-+    private V1911() {}
-+
 +    public static void register() {
 +        MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) {
 +            @Override
@@ -12072,6 +14140,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        });
 +    }
 +
++    private V1911() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1914.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1914.java
 new file mode 100644
@@ -12088,7 +14157,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V1914 {
 +
-+    protected static final int VERSION = MCVersions.V18W48A;
++    private static final int VERSION = MCVersions.V18W48A;
 +
 +    public static void register() {
 +        MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:chest", new DataConverter<>(VERSION) {
@@ -12105,6 +14174,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        });
 +    }
 +
++    private V1914() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1917.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1917.java
 new file mode 100644
@@ -12121,9 +14191,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V1917 {
 +
-+    protected static final int VERSION = MCVersions.V18W49A + 1;
-+
-+    private V1917() {}
++    private static final int VERSION = MCVersions.V18W49A + 1;
 +
 +    public static void register() {
 +        MCTypeRegistry.ENTITY.addConverterForId("minecraft:cat", new DataConverter<>(VERSION) {
@@ -12137,6 +14205,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        });
 +    }
 +
++    private V1917() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1918.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1918.java
 new file mode 100644
@@ -12154,9 +14223,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V1918 {
 +
-+    protected static final int VERSION = MCVersions.V18W49A + 2;
-+
-+    private V1918() {}
++    private static final int VERSION = MCVersions.V18W49A + 2;
 +
 +    private static String getProfessionString(final int professionId, final int careerId) {
 +        if (professionId == 0) {
@@ -12208,6 +14275,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        MCTypeRegistry.ENTITY.addConverterForId("minecraft:villager", converter);
 +        MCTypeRegistry.ENTITY.addConverterForId("minecraft:zombie_villager", converter);
 +    }
++
++    private V1918() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1920.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1920.java
 new file mode 100644
@@ -12226,9 +14295,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V1920 {
 +
-+    protected static final int VERSION = MCVersions.V18W50A + 1;
-+
-+    private V1920() {}
++    private static final int VERSION = MCVersions.V18W50A + 1;
 +
 +    public static void register() {
 +        MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) {
@@ -12289,6 +14356,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +        MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:campfire", new DataWalkerItemLists("Items"));
 +    }
++
++    private V1920() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1925.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1925.java
 new file mode 100644
@@ -12306,7 +14375,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V1925 {
 +
-+    protected static final int VERSION = MCVersions.V19W03C + 1;
++    private static final int VERSION = MCVersions.V19W03C + 1;
 +
 +    public static void register() {
 +        MCTypeRegistry.SAVED_DATA_MAP_DATA.addStructureConverter(new DataConverter<>(VERSION) {
@@ -12324,6 +14393,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        });
 +    }
 +
++    private V1925() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1928.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1928.java
 new file mode 100644
@@ -12336,30 +14406,33 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
 +import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterAbstractEntityRename;
 +import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename;
-+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
-+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists;
 +import com.google.common.collect.ImmutableMap;
++import java.util.HashMap;
 +
 +public final class V1928 {
 +
-+    protected static final int VERSION = MCVersions.V19W04B + 1;
-+
-+    private V1928() {}
++    private static final int VERSION = MCVersions.V19W04B + 1;
 +
 +    private static void registerMob(final String id) {
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems"));
++        V100.registerEquipment(VERSION, id);
 +    }
 +
 +    public static void register() {
-+        ConverterAbstractEntityRename.register(VERSION, ImmutableMap.of(
-+                "minecraft:illager_beast", "minecraft:ravager"
++        ConverterAbstractEntityRename.register(VERSION, new HashMap<>(
++                ImmutableMap.of(
++                        "minecraft:illager_beast", "minecraft:ravager"
++                )
 +        )::get);
-+        ConverterAbstractItemRename.register(VERSION, ImmutableMap.of(
-+                "minecraft:illager_beast_spawn_egg", "minecraft:ravager_spawn_egg"
++        ConverterAbstractItemRename.register(VERSION, new HashMap<>(
++                ImmutableMap.of(
++                        "minecraft:illager_beast_spawn_egg", "minecraft:ravager_spawn_egg"
++                )
 +        )::get);
 +
 +        registerMob("minecraft:ravager");
 +    }
++
++    private V1928() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1929.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1929.java
 new file mode 100644
@@ -12372,49 +14445,34 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
 +import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
 +import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils;
-+import ca.spottedleaf.dataconverter.types.ListType;
 +import ca.spottedleaf.dataconverter.types.MapType;
-+import ca.spottedleaf.dataconverter.types.ObjectType;
 +
 +public final class V1929 {
 +
-+    protected static final int VERSION = MCVersions.V19W04B + 2;
-+
-+    private V1929() {}
++    private static final int VERSION = MCVersions.V19W04B + 2;
 +
 +    public static void register() {
 +        MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:wandering_trader", (final MapType<String> data, final long fromVersion, final long toVersion) -> {
 +            WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "Inventory", fromVersion, toVersion);
 +
-+            final MapType<String> offers = data.getMap("Offers");
-+            if (offers != null) {
-+                final ListType recipes = offers.getList("Recipes", ObjectType.MAP);
-+                if (recipes != null) {
-+                    for (int i = 0, len = recipes.size(); i < len; ++i) {
-+                        final MapType<String> recipe = recipes.getMap(i);
-+                        WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "buy", fromVersion, toVersion);
-+                        WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "buyB", fromVersion, toVersion);
-+                        WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "sell", fromVersion, toVersion);
-+                    }
-+                }
-+            }
-+
-+            WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "ArmorItems", fromVersion, toVersion);
-+            WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "HandItems", fromVersion, toVersion);
++            WalkerUtils.convertList(MCTypeRegistry.VILLAGER_TRADE, data.getMap("Offers"), "Recipes", fromVersion, toVersion);
 +
 +            return null;
 +        });
++        V100.registerEquipment(VERSION, "minecraft:wandering_trader");
++
 +        MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:trader_llama", (final MapType<String> data, final long fromVersion, final long toVersion) -> {
 +            WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, data, "SaddleItem", fromVersion, toVersion);
 +            WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, data, "DecorItem", fromVersion, toVersion);
 +
 +            WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "Items", fromVersion, toVersion);
-+            WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "ArmorItems", fromVersion, toVersion);
-+            WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "HandItems", fromVersion, toVersion);
 +
 +            return null;
 +        });
++        V100.registerEquipment(VERSION, "minecraft:trader_llama");
 +    }
++
++    private V1929() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1931.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1931.java
 new file mode 100644
@@ -12425,23 +14483,21 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
-+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
-+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists;
 +
 +public final class V1931 {
 +
-+    protected static final int VERSION = MCVersions.V19W06A;
-+
-+    private V1931() {}
++    private static final int VERSION = MCVersions.V19W06A;
 +
 +    private static void registerMob(final String id) {
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems"));
++        V100.registerEquipment(VERSION, id);
 +    }
 +
 +    public static void register() {
 +        registerMob("minecraft:fox");
 +    }
 +
++    private V1931() {}
++
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1936.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1936.java
 new file mode 100644
@@ -12458,9 +14514,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V1936 {
 +
-+    protected static final int VERSION = MCVersions.V19W09A + 1;
-+
-+    private V1936() {}
++    private static final int VERSION = MCVersions.V19W09A + 1;
 +
 +    public static void register() {
 +        MCTypeRegistry.OPTIONS.addStructureConverter(new DataConverter<>(VERSION) {
@@ -12485,6 +14539,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            return 0.5D;
 +        }
 +    }
++
++    private V1936() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1946.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1946.java
 new file mode 100644
@@ -12502,9 +14558,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V1946 {
 +
-+    protected static final int VERSION = MCVersions.V19W14B + 1;
-+
-+    private V1946() {}
++    private static final int VERSION = MCVersions.V19W14B + 1;
 +
 +    public static void register() {
 +        MCTypeRegistry.POI_CHUNK.addStructureConverter(new DataConverter<>(VERSION) {
@@ -12533,6 +14587,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        });
 +    }
 +
++    private V1946() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1948.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1948.java
 new file mode 100644
@@ -12549,9 +14604,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V1948 {
 +
-+    protected static final int VERSION = MCVersions.V1_14_PRE2;
-+
-+    private V1948() {}
++    private static final int VERSION = MCVersions.V1_14_PRE2;
 +
 +    public static void register() {
 +        MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:white_banner", new DataConverter<>(VERSION) {
@@ -12578,6 +14631,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            }
 +        });
 +    }
++
++    private V1948() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1953.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1953.java
 new file mode 100644
@@ -12594,9 +14649,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V1953 {
 +
-+    protected static final int VERSION = MCVersions.V1_14 + 1;
-+
-+    private V1953() {}
++    private static final int VERSION = MCVersions.V1_14 + 1;
 +
 +    public static void register() {
 +        MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:banner", new DataConverter<>(VERSION) {
@@ -12611,6 +14664,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        });
 +    }
 +
++    private V1953() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1955.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1955.java
 new file mode 100644
@@ -12631,7 +14685,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V1955 {
 +
-+    protected static final int VERSION = MCVersions.V1_14_1_PRE1;
++    private static final int VERSION = MCVersions.V1_14_1_PRE1;
 +
 +    private static final int[] LEVEL_XP_THRESHOLDS = new int[] {
 +            0,
@@ -12641,13 +14695,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            150
 +    };
 +
-+    private V1955() {}
-+
-+    static int getMinXpPerLevel(final int level) {
++    private static int getMinXpPerLevel(final int level) {
 +        return LEVEL_XP_THRESHOLDS[Mth.clamp(level - 1, 0, LEVEL_XP_THRESHOLDS.length - 1)];
 +    }
 +
-+    static void addLevel(final MapType<String> data, final int level) {
++    private static void addLevel(final MapType<String> data, final int level) {
 +        MapType<String> villagerData = data.getMap("VillagerData");
 +        if (villagerData == null) {
 +            villagerData = Types.NBT.createEmptyMap();
@@ -12656,7 +14708,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        villagerData.setInt("level", level);
 +    }
 +
-+    static void addXpFromLevel(final MapType<String> data, final int level) {
++    private static void addXpFromLevel(final MapType<String> data, final int level) {
 +        data.setInt("Xp", getMinXpPerLevel(level));
 +    }
 +
@@ -12711,6 +14763,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        });
 +    }
 +
++    private V1955() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1961.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1961.java
 new file mode 100644
@@ -12727,9 +14780,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V1961 {
 +
-+    protected static final int VERSION = MCVersions.V1_14_2_PRE3 + 1;
-+
-+    private V1961() {}
++    private static final int VERSION = MCVersions.V1_14_2_PRE3 + 1;
 +
 +    public static void register() {
 +        MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) {
@@ -12747,6 +14798,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        });
 +    }
 +
++    private V1961() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1963.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1963.java
 new file mode 100644
@@ -12765,9 +14817,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V1963 {
 +
-+    protected static final int VERSION = MCVersions.V1_14_2;
-+
-+    private V1963() {}
++    private static final int VERSION = MCVersions.V1_14_2;
 +
 +    public static void register() {
 +        MCTypeRegistry.ENTITY.addConverterForId("minecraft:villager", new DataConverter<>(VERSION) {
@@ -12793,6 +14843,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        });
 +    }
 +
++    private V1963() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2100.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2100.java
 new file mode 100644
@@ -12807,30 +14858,33 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.minecraft.converters.recipe.ConverterAbstractRecipeRename;
 +import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
 +import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils;
-+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists;
 +import ca.spottedleaf.dataconverter.types.ListType;
 +import ca.spottedleaf.dataconverter.types.ObjectType;
 +import com.google.common.collect.ImmutableMap;
++import java.util.HashMap;
 +import java.util.Map;
 +
 +public final class V2100 {
 +
-+    protected static final int VERSION = MCVersions.V1_14_4 + 124;
-+    protected static final Map<String, String> RECIPE_RENAMES = ImmutableMap.of(
-+            "minecraft:sugar", "sugar_from_sugar_cane"
++    private static final int VERSION = MCVersions.V1_14_4 + 124;
++    private static final Map<String, String> RECIPE_RENAMES = new HashMap<>(
++            ImmutableMap.of(
++                    "minecraft:sugar", "minecraft:sugar_from_sugar_cane"
++            )
++    );
++    private static final Map<String, String> ADVANCEMENT_RENAMES = new HashMap<>(
++            ImmutableMap.of(
++                    "minecraft:recipes/misc/sugar", "minecraft:recipes/misc/sugar_from_sugar_cane"
++            )
 +    );
 +
-+    private V2100() {}
-+
 +    private static void registerMob(final String id) {
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems"));
++        V100.registerEquipment(VERSION, id);
 +    }
 +
 +    public static void register() {
 +        ConverterAbstractRecipeRename.register(VERSION, RECIPE_RENAMES::get);
-+        ConverterAbstractAdvancementsRename.register(VERSION, ImmutableMap.of(
-+                "minecraft:recipes/misc/sugar", "minecraft:recipes/misc/sugar_from_sugar_cane"
-+        )::get);
++        ConverterAbstractAdvancementsRename.register(VERSION, ADVANCEMENT_RENAMES::get);
 +
 +        registerMob("minecraft:bee");
 +        registerMob("minecraft:bee_stinger");
@@ -12845,6 +14899,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            return null;
 +        });
 +    }
++
++    private V2100() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2202.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2202.java
 new file mode 100644
@@ -12861,9 +14917,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V2202 {
 +
-+    protected static final int VERSION = MCVersions.V19W35A + 1;
-+
-+    private V2202() {}
++    private static final int VERSION = MCVersions.V19W35A + 1;
 +
 +    public static void register() {
 +        MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) {
@@ -12883,24 +14937,25 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                final int[] newBiomes = new int[1024];
 +                level.setInts("Biomes", newBiomes);
 +
-+                int n;
-+                for(n = 0; n < 4; ++n) {
-+                    for(int j = 0; j < 4; ++j) {
++                for (int i = 0; i < 4; ++i) {
++                    for (int j = 0; j < 4; ++j) {
 +                        int k = (j << 2) + 2;
-+                        int l = (n << 2) + 2;
++                        int l = (i << 2) + 2;
 +                        int m = l << 4 | k;
-+                        newBiomes[n << 2 | j] = oldBiomes[m];
++                        newBiomes[i << 2 | j] = oldBiomes[m];
 +                    }
 +                }
 +
-+                for(n = 1; n < 64; ++n) {
-+                    System.arraycopy(newBiomes, 0, newBiomes, n * 16, 16);
++                for (int i = 1; i < 64; ++i) {
++                    System.arraycopy(newBiomes, 0, newBiomes, i * 16, 16);
 +                }
 +
 +                return null;
 +            }
 +        });
 +    }
++
++    private V2202() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2209.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2209.java
 new file mode 100644
@@ -12915,17 +14970,18 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename;
 +import ca.spottedleaf.dataconverter.minecraft.converters.poi.ConverterAbstractPOIRename;
 +import com.google.common.collect.ImmutableMap;
++import java.util.HashMap;
 +import java.util.Map;
 +
 +public final class V2209 {
 +
-+    protected static final int VERSION = MCVersions.V19W40A + 1;
-+
-+    private V2209() {}
++    private static final int VERSION = MCVersions.V19W40A + 1;
 +
 +    public static void register() {
-+        final Map<String, String> renamedIds = ImmutableMap.of(
-+                "minecraft:bee_hive", "minecraft:beehive"
++        final Map<String, String> renamedIds = new HashMap<>(
++                ImmutableMap.of(
++                        "minecraft:bee_hive", "minecraft:beehive"
++                )
 +        );
 +
 +        ConverterAbstractBlockRename.register(VERSION, renamedIds::get);
@@ -12933,6 +14989,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        ConverterAbstractPOIRename.register(VERSION, renamedIds::get);
 +    }
 +
++    private V2209() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2211.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2211.java
 new file mode 100644
@@ -12950,9 +15007,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V2211 {
 +
-+    protected static final int VERSION = MCVersions.V19W41A + 1;
-+
-+    private V2211() {}
++    private static final int VERSION = MCVersions.V19W41A + 1;
 +
 +    public static void register() {
 +        MCTypeRegistry.STRUCTURE_FEATURE.addStructureConverter(new DataConverter<>(VERSION) {
@@ -12971,6 +15026,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        });
 +    }
 +
++    private V2211() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2218.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2218.java
 new file mode 100644
@@ -12987,9 +15043,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V2218 {
 +
-+    protected static final int VERSION = MCVersions.V1_15_PRE1;
-+
-+    private V2218() {}
++    private static final int VERSION = MCVersions.V1_15_PRE1;
 +
 +    public static void register() {
 +        MCTypeRegistry.POI_CHUNK.addStructureConverter(new DataConverter<>(VERSION) {
@@ -13010,6 +15064,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            }
 +        });
 +    }
++
++    private V2218() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2501.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2501.java
 new file mode 100644
@@ -13028,9 +15084,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V2501 {
 +
-+    protected static final int VERSION = MCVersions.V1_15_2 + 271;
-+
-+    private V2501() {}
++    private static final int VERSION = MCVersions.V1_15_2 + 271;
 +
 +    private static void registerFurnace(final String id) {
 +        MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, id, (data, fromVersion, toVersion) -> {
@@ -13081,6 +15135,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        registerFurnace("minecraft:smoker");
 +        registerFurnace("minecraft:blast_furnace");
 +    }
++
++    private V2501() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2502.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2502.java
 new file mode 100644
@@ -13091,22 +15147,20 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
-+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
-+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists;
 +
 +public final class V2502 {
 +
-+    protected static final int VERSION = MCVersions.V1_15_2 + 272;
-+
-+    private V2502() {}
++    private static final int VERSION = MCVersions.V1_15_2 + 272;
 +
 +    private static void registerMob(final String id) {
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems"));
++        V100.registerEquipment(VERSION, id);
 +    }
 +
 +    public static void register() {
 +        registerMob("minecraft:hoglin");
 +    }
++
++    private V2502() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2503.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2503.java
 new file mode 100644
@@ -13123,31 +15177,33 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.types.MapType;
 +import com.google.common.collect.ImmutableMap;
 +import com.google.common.collect.ImmutableSet;
++import java.util.HashMap;
++import java.util.HashSet;
 +import java.util.Set;
 +
 +public final class V2503 {
 +
-+    protected static final int VERSION = MCVersions.V1_15_2 + 273;
++    private static final int VERSION = MCVersions.V1_15_2 + 273;
 +
-+    private static final Set<String> WALL_BLOCKS = ImmutableSet.of(
-+            "minecraft:andesite_wall",
-+            "minecraft:brick_wall",
-+            "minecraft:cobblestone_wall",
-+            "minecraft:diorite_wall",
-+            "minecraft:end_stone_brick_wall",
-+            "minecraft:granite_wall",
-+            "minecraft:mossy_cobblestone_wall",
-+            "minecraft:mossy_stone_brick_wall",
-+            "minecraft:nether_brick_wall",
-+            "minecraft:prismarine_wall",
-+            "minecraft:red_nether_brick_wall",
-+            "minecraft:red_sandstone_wall",
-+            "minecraft:sandstone_wall",
-+            "minecraft:stone_brick_wall"
++    private static final Set<String> WALL_BLOCKS = new HashSet<>(
++            ImmutableSet.of(
++                    "minecraft:andesite_wall",
++                    "minecraft:brick_wall",
++                    "minecraft:cobblestone_wall",
++                    "minecraft:diorite_wall",
++                    "minecraft:end_stone_brick_wall",
++                    "minecraft:granite_wall",
++                    "minecraft:mossy_cobblestone_wall",
++                    "minecraft:mossy_stone_brick_wall",
++                    "minecraft:nether_brick_wall",
++                    "minecraft:prismarine_wall",
++                    "minecraft:red_nether_brick_wall",
++                    "minecraft:red_sandstone_wall",
++                    "minecraft:sandstone_wall",
++                    "minecraft:stone_brick_wall"
++            )
 +    );
 +
-+    private V2503() {}
-+
 +    private static void changeWallProperty(final MapType<String> properties, final String path) {
 +        final String property = properties.getString(path);
 +        if (property != null) {
@@ -13176,10 +15232,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                return null;
 +            }
 +        });
-+        ConverterAbstractAdvancementsRename.register(VERSION, ImmutableMap.of(
-+                "minecraft:recipes/misc/composter", "minecraft:recipes/decorations/composter"
++        ConverterAbstractAdvancementsRename.register(VERSION, new HashMap<>(
++                ImmutableMap.of(
++                        "minecraft:recipes/misc/composter", "minecraft:recipes/decorations/composter"
++                )
 +        )::get);
 +    }
++
++    private V2503() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2505.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2505.java
 new file mode 100644
@@ -13192,18 +15252,15 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
 +import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
-+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists;
 +import ca.spottedleaf.dataconverter.types.MapType;
 +import ca.spottedleaf.dataconverter.types.Types;
 +
 +public final class V2505 {
 +
-+    protected static final int VERSION = MCVersions.V20W06A + 1;
-+
-+    private V2505() {}
++    private static final int VERSION = MCVersions.V20W06A + 1;
 +
 +    private static void registerMob(final String id) {
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems"));
++        V100.registerEquipment(VERSION, id);
 +    }
 +
 +    public static void register() {
@@ -13235,6 +15292,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +        registerMob("minecraft:piglin");
 +    }
++
++    private V2505() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2508.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2508.java
 new file mode 100644
@@ -13248,23 +15307,26 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename;
 +import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename;
 +import com.google.common.collect.ImmutableMap;
++import java.util.HashMap;
 +import java.util.Map;
 +
 +public final class V2508 {
 +
-+    protected static final int VERSION = MCVersions.V20W08A + 1;
-+
-+    private V2508() {}
++    private static final int VERSION = MCVersions.V20W08A + 1;
 +
 +    public static void register() {
-+        final Map<String, String> remap = ImmutableMap.of(
-+                "minecraft:warped_fungi", "minecraft:warped_fungus",
-+                "minecraft:crimson_fungi", "minecraft:crimson_fungus"
++        final Map<String, String> remap = new HashMap<>(
++                ImmutableMap.of(
++                        "minecraft:warped_fungi", "minecraft:warped_fungus",
++                        "minecraft:crimson_fungi", "minecraft:crimson_fungus"
++                )
 +        );
 +
 +        ConverterAbstractBlockRename.register(VERSION, remap::get);
 +        ConverterAbstractItemRename.register(VERSION, remap::get);
 +    }
++
++    private V2508() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2509.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2509.java
 new file mode 100644
@@ -13277,30 +15339,33 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
 +import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterAbstractEntityRename;
 +import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename;
-+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
-+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists;
 +import com.google.common.collect.ImmutableMap;
++import java.util.HashMap;
 +
 +public final class V2509 {
 +
-+    protected static final int VERSION = MCVersions.V20W08A + 2;
-+
-+    private V2509() {}
++    private static final int VERSION = MCVersions.V20W08A + 2;
 +
 +    private static void registerMob(final String id) {
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems"));
++        V100.registerEquipment(VERSION, id);
 +    }
 +
 +    public static void register() {
-+        ConverterAbstractItemRename.register(VERSION, ImmutableMap.of(
-+                "minecraft:zombie_pigman_spawn_egg", "minecraft:zombified_piglin_spawn_egg"
++        ConverterAbstractItemRename.register(VERSION, new HashMap<>(
++                ImmutableMap.of(
++                        "minecraft:zombie_pigman_spawn_egg", "minecraft:zombified_piglin_spawn_egg"
++                )
 +        )::get);
-+        ConverterAbstractEntityRename.register(VERSION, ImmutableMap.of(
-+                "minecraft:zombie_pigman", "minecraft:zombified_piglin"
++        ConverterAbstractEntityRename.register(VERSION, new HashMap<>(
++                ImmutableMap.of(
++                        "minecraft:zombie_pigman", "minecraft:zombified_piglin"
++                )
 +        )::get);
 +
 +        registerMob("minecraft:zombified_piglin");
 +    }
++
++    private V2509() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2511.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2511.java
 new file mode 100644
@@ -13319,9 +15384,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V2511 {
 +
-+    protected static final int VERSION = MCVersions.V20W09A + 1;
-+
-+    private V2511() {}
++    private static final int VERSION = MCVersions.V20W09A + 1;
 +
 +    private static int[] createUUIDArray(final long most, final long least) {
 +        return new int[] {
@@ -13404,6 +15467,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        // Vanilla migrates the potion item but does not change the schema.
 +        MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:potion", new DataWalkerItems("Item"));
 +    }
++
++    private V2511() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2514.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2514.java
 new file mode 100644
@@ -13426,7 +15491,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V2514 {
 +
-+    protected static final int VERSION = MCVersions.V20W11A + 1;
++    private static final int VERSION = MCVersions.V20W11A + 1;
 +
 +    private static final Set<String> ABSTRACT_HORSES = Sets.newHashSet();
 +    private static final Set<String> TAMEABLE_ANIMALS = Sets.newHashSet();
@@ -13590,8 +15655,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        }
 +    }
 +
-+    private V2514() {}
-+
 +    private static void updatePiglin(final MapType<String> data) {
 +        final MapType<String> brain = data.getMap("Brain");
 +        if (brain == null) {
@@ -14000,6 +16063,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    private static void updateSkullOwner(final MapType<String> tag) {
 +        replaceUUIDString(tag.getMap("SkullOwner"), "Id", "Id");
 +    }
++
++    private V2514() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2516.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2516.java
 new file mode 100644
@@ -14018,9 +16083,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V2516 {
 +
-+    protected static final int VERSION = MCVersions.V20W12A + 1;
-+
-+    private V2516() {}
++    private static final int VERSION = MCVersions.V20W12A + 1;
 +
 +    public static void register() {
 +        final DataConverter<MapType<String>, MapType<String>> gossipUUIDConverter = new DataConverter<>(VERSION) {
@@ -14043,6 +16106,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        MCTypeRegistry.ENTITY.addConverterForId("minecraft:villager", gossipUUIDConverter);
 +        MCTypeRegistry.ENTITY.addConverterForId("minecraft:zombie_villager", gossipUUIDConverter);
 +    }
++
++    private V2516() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2518.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2518.java
 new file mode 100644
@@ -14062,7 +16127,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V2518 {
 +
-+    protected static final int VERSION = MCVersions.V20W12A + 3;
++    private static final int VERSION = MCVersions.V20W12A + 3;
 +
 +    private static final Map<String, String> FACING_RENAMES = new HashMap<>(
 +            ImmutableMap.<String, String>builder()
@@ -14075,9 +16140,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                    .build()
 +    );
 +
-+
-+    private V2518() {}
-+
 +    public static void register() {
 +        MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:jigsaw", new DataConverter<>(VERSION) {
 +            @Override
@@ -14115,6 +16177,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            }
 +        });
 +    }
++
++    private V2518() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2519.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2519.java
 new file mode 100644
@@ -14125,22 +16189,20 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
-+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
-+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists;
 +
 +public final class V2519 {
 +
-+    protected static final int VERSION = MCVersions.V20W12A + 4;
-+
-+    private V2519() {}
++    private static final int VERSION = MCVersions.V20W12A + 4;
 +
 +    private static void registerMob(final String id) {
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems"));
++        V100.registerEquipment(VERSION, id);
 +    }
 +
 +    public static void register() {
 +        registerMob("minecraft:strider");
 +    }
++
++    private V2519() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2522.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2522.java
 new file mode 100644
@@ -14151,22 +16213,20 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
-+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
-+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists;
 +
 +public final class V2522 {
 +
-+    protected static final int VERSION = MCVersions.V20W13B + 1;
-+
-+    private V2522() {}
++    private static final int VERSION = MCVersions.V20W13B + 1;
 +
 +    private static void registerMob(final String id) {
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems"));
++        V100.registerEquipment(VERSION, id);
 +    }
 +
 +    public static void register() {
 +        registerMob("minecraft:zoglin");
 +    }
++
++    private V2522() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2523.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2523.java
 new file mode 100644
@@ -14176,98 +16236,44 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 @@ -0,0 +0,0 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
-+import ca.spottedleaf.dataconverter.converters.DataConverter;
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
-+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
-+import ca.spottedleaf.dataconverter.types.ListType;
-+import ca.spottedleaf.dataconverter.types.MapType;
-+import ca.spottedleaf.dataconverter.types.ObjectType;
++import ca.spottedleaf.dataconverter.minecraft.converters.attributes.ConverterAbstractAttributesRename;
 +import com.google.common.collect.ImmutableMap;
 +import java.util.HashMap;
 +import java.util.Map;
 +
 +public final class V2523 {
 +
-+    protected static final int VERSION = MCVersions.V20W13B + 2;
++    private static final int VERSION = MCVersions.V20W13B + 2;
 +
 +    private static final Map<String, String> RENAMES = new HashMap<>(
 +            ImmutableMap.<String, String>builder()
-+                    .put("generic.maxHealth", "generic.max_health")
-+                    .put("Max Health", "generic.max_health")
-+                    .put("zombie.spawnReinforcements", "zombie.spawn_reinforcements")
-+                    .put("Spawn Reinforcements Chance", "zombie.spawn_reinforcements")
-+                    .put("horse.jumpStrength", "horse.jump_strength")
-+                    .put("Jump Strength", "horse.jump_strength")
-+                    .put("generic.followRange", "generic.follow_range")
-+                    .put("Follow Range", "generic.follow_range")
-+                    .put("generic.knockbackResistance", "generic.knockback_resistance")
-+                    .put("Knockback Resistance", "generic.knockback_resistance")
-+                    .put("generic.movementSpeed", "generic.movement_speed")
-+                    .put("Movement Speed", "generic.movement_speed")
-+                    .put("generic.flyingSpeed", "generic.flying_speed")
-+                    .put("Flying Speed", "generic.flying_speed")
-+                    .put("generic.attackDamage", "generic.attack_damage")
-+                    .put("generic.attackKnockback", "generic.attack_knockback")
-+                    .put("generic.attackSpeed", "generic.attack_speed")
-+                    .put("generic.armorToughness", "generic.armor_toughness")
++                    .put("generic.maxHealth", "minecraft:generic.max_health")
++                    .put("Max Health", "minecraft:generic.max_health")
++                    .put("zombie.spawnReinforcements", "minecraft:zombie.spawn_reinforcements")
++                    .put("Spawn Reinforcements Chance", "minecraft:zombie.spawn_reinforcements")
++                    .put("horse.jumpStrength", "minecraft:horse.jump_strength")
++                    .put("Jump Strength", "minecraft:horse.jump_strength")
++                    .put("generic.followRange", "minecraft:generic.follow_range")
++                    .put("Follow Range", "minecraft:generic.follow_range")
++                    .put("generic.knockbackResistance", "minecraft:generic.knockback_resistance")
++                    .put("Knockback Resistance", "minecraft:generic.knockback_resistance")
++                    .put("generic.movementSpeed", "minecraft:generic.movement_speed")
++                    .put("Movement Speed", "minecraft:generic.movement_speed")
++                    .put("generic.flyingSpeed", "minecraft:generic.flying_speed")
++                    .put("Flying Speed", "minecraft:generic.flying_speed")
++                    .put("generic.attackDamage", "minecraft:generic.attack_damage")
++                    .put("generic.attackKnockback", "minecraft:generic.attack_knockback")
++                    .put("generic.attackSpeed", "minecraft:generic.attack_speed")
++                    .put("generic.armorToughness", "minecraft:generic.armor_toughness")
 +                    .build()
 +    );
 +
-+    private V2523() {}
-+
-+    private static void updateName(final MapType<String> data, final String path) {
-+        if (data == null) {
-+            return;
-+        }
-+
-+        final String name = data.getString(path);
-+        if (name != null) {
-+            final String renamed = RENAMES.get(name);
-+            if (renamed != null) {
-+                data.setString(path, renamed);
-+            }
-+        }
-+    }
-+
 +    public static void register() {
-+        final DataConverter<MapType<String>, MapType<String>> entityConverter = new DataConverter<>(VERSION) {
-+            @Override
-+            public MapType<String> convert(final MapType<String> data, final long sourceVersion, final long toVersion) {
-+                final ListType attributes = data.getList("Attributes", ObjectType.MAP);
-+
-+                if (attributes == null) {
-+                    return null;
-+                }
-+
-+                for (int i = 0, len = attributes.size(); i < len; ++i) {
-+                    updateName(attributes.getMap(i), "Name");
-+                }
-+
-+                return null;
-+            }
-+        };
-+
-+        MCTypeRegistry.ENTITY.addStructureConverter(entityConverter);
-+        MCTypeRegistry.PLAYER.addStructureConverter(entityConverter);
-+
-+        MCTypeRegistry.ITEM_STACK.addStructureConverter(new DataConverter<>(VERSION) {
-+            @Override
-+            public MapType<String> convert(final MapType<String> data, final long sourceVersion, final long toVersion) {
-+                final ListType attributes = data.getList("AttributeModifiers", ObjectType.MAP);
-+
-+                if (attributes == null) {
-+                    return null;
-+                }
-+
-+                for (int i = 0, len = attributes.size(); i < len; ++i) {
-+                    updateName(attributes.getMap(i), "AttributeName");
-+                }
-+
-+                return null;
-+            }
-+        });
++        ConverterAbstractAttributesRename.register(VERSION, RENAMES::get);
 +    }
 +
++    private V2523() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2527.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2527.java
 new file mode 100644
@@ -14288,9 +16294,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V2527 {
 +
-+    protected static final int VERSION = MCVersions.V20W16A + 1;
-+
-+    private V2527() {}
++    private static final int VERSION = MCVersions.V20W16A + 1;
 +
 +    public static void register() {
 +        MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) {
@@ -14397,6 +16401,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            return padded;
 +        }
 +    }
++
++    private V2527() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2528.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2528.java
 new file mode 100644
@@ -14410,24 +16416,29 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename;
 +import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename;
 +import com.google.common.collect.ImmutableMap;
++import java.util.HashMap;
 +
 +public final class V2528 {
 +
-+    protected static final int VERSION = MCVersions.V20W16A + 2;
-+
-+    private V2528() {}
++    private static final int VERSION = MCVersions.V20W16A + 2;
 +
 +    public static void register() {
-+        ConverterAbstractItemRename.register(VERSION, ImmutableMap.of(
-+                "minecraft:soul_fire_torch", "minecraft:soul_torch",
-+                "minecraft:soul_fire_lantern", "minecraft:soul_lantern"
++        ConverterAbstractItemRename.register(VERSION, new HashMap<>(
++                ImmutableMap.of(
++                        "minecraft:soul_fire_torch", "minecraft:soul_torch",
++                        "minecraft:soul_fire_lantern", "minecraft:soul_lantern"
++                )
 +        )::get);
-+        ConverterAbstractBlockRename.register(VERSION, ImmutableMap.of(
-+                "minecraft:soul_fire_torch", "minecraft:soul_torch",
-+                "minecraft:soul_fire_wall_torch", "minecraft:soul_wall_torch",
-+                "minecraft:soul_fire_lantern", "minecraft:soul_lantern"
++        ConverterAbstractBlockRename.register(VERSION, new HashMap<>(
++                ImmutableMap.of(
++                        "minecraft:soul_fire_torch", "minecraft:soul_torch",
++                        "minecraft:soul_fire_wall_torch", "minecraft:soul_wall_torch",
++                        "minecraft:soul_fire_lantern", "minecraft:soul_lantern"
++                )
 +        )::get);
 +    }
++
++    private V2528() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2529.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2529.java
 new file mode 100644
@@ -14444,9 +16455,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V2529 {
 +
-+    protected static final int VERSION = MCVersions.V20W17A;
-+
-+    private V2529() {}
++    private static final int VERSION = MCVersions.V20W17A;
 +
 +    public static void register() {
 +        MCTypeRegistry.ENTITY.addConverterForId("minecraft:strider", new DataConverter<>(VERSION) {
@@ -14459,6 +16468,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            }
 +        });
 +    }
++
++    private V2529() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2531.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2531.java
 new file mode 100644
@@ -14475,9 +16486,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V2531 {
 +
-+    protected static final int VERSION = MCVersions.V20W17A + 2;
-+
-+    private V2531() {}
++    private static final int VERSION = MCVersions.V20W17A + 2;
 +
 +    private static boolean isConnected(final String facing) {
 +        return !"none".equals(facing);
@@ -14528,6 +16537,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            }
 +        });
 +    }
++
++    private V2531() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2533.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2533.java
 new file mode 100644
@@ -14546,9 +16557,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V2533 {
 +
-+    protected static final int VERSION = MCVersions.V20W18A + 1;
-+
-+    private V2533() {}
++    private static final int VERSION = MCVersions.V20W18A + 1;
 +
 +    public static void register() {
 +        MCTypeRegistry.ENTITY.addConverterForId("minecraft:villager", new DataConverter<>(VERSION) {
@@ -14577,6 +16586,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        });
 +    }
 +
++    private V2533() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2535.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2535.java
 new file mode 100644
@@ -14595,9 +16605,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V2535 {
 +
-+    protected static final int VERSION = MCVersions.V20W19A + 1;
-+
-+    private V2535() {}
++    private static final int VERSION = MCVersions.V20W19A + 1;
 +
 +    public static void register() {
 +        MCTypeRegistry.ENTITY.addConverterForId("minecraft:shulker", new DataConverter<>(VERSION) {
@@ -14617,6 +16625,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            }
 +        });
 +    }
++
++    private V2535() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2538.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2538.java
 new file mode 100644
@@ -14664,6 +16674,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            }
 +        });
 +    }
++
++    private V2538() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2550.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2550.java
 new file mode 100644
@@ -14688,7 +16700,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V2550 {
 +
-+    protected static final int VERSION = MCVersions.V20W20B + 13;
++    private static final int VERSION = MCVersions.V20W20B + 13;
 +
 +    private static final Map<String, StructureFeatureConfiguration> DEFAULTS = new HashMap<>(
 +            ImmutableMap.<String, StructureFeatureConfiguration>builder()
@@ -14704,7 +16716,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                    .build()
 +    );
 +
-+    record StructureFeatureConfiguration(int spacing, int separation, int salt) {
++    private static record StructureFeatureConfiguration(int spacing, int separation, int salt) {
 +
 +        public MapType<String> serialize() {
 +            final MapType<String> ret = Types.NBT.createEmptyMap();
@@ -15014,6 +17026,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +        structures.put(structureName, new StructureFeatureConfiguration(newSpacing, structure.separation, structure.salt));
 +    }
++
++    private V2550() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2551.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2551.java
 new file mode 100644
@@ -15032,7 +17046,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V2551 {
 +
-+    protected static final int VERSION = MCVersions.V20W20B + 14;
++    private static final int VERSION = MCVersions.V20W20B + 14;
 +
 +    public static void register() {
 +        MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureWalker(VERSION, (final MapType<String> data, final long fromVersion, final long toVersion) -> {
@@ -15121,6 +17135,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            return null;
 +        });
 +    }
++
++    private V2551() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2552.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2552.java
 new file mode 100644
@@ -15134,19 +17150,21 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.minecraft.converters.helpers.ConverterAbstractStringValueTypeRename;
 +import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
 +import com.google.common.collect.ImmutableMap;
++import java.util.HashMap;
 +
 +public final class V2552 {
 +
-+    protected static final int VERSION = MCVersions.V20W20B + 15;
-+
-+    private V2552() {}
++    private static final int VERSION = MCVersions.V20W20B + 15;
 +
 +    public static void register() {
-+        ConverterAbstractStringValueTypeRename.register(VERSION, MCTypeRegistry.BIOME, ImmutableMap.of(
-+                "minecraft:nether", "minecraft:nether_wastes"
++        ConverterAbstractStringValueTypeRename.register(VERSION, MCTypeRegistry.BIOME, new HashMap<>(
++                ImmutableMap.of(
++                        "minecraft:nether", "minecraft:nether_wastes"
++                )
 +        )::get);
 +    }
 +
++    private V2552() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2553.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2553.java
 new file mode 100644
@@ -15165,7 +17183,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V2553 {
 +
-+    protected static final int VERSION = MCVersions.V20W20B + 16;
++    private static final int VERSION = MCVersions.V20W20B + 16;
 +
 +    public static final Map<String, String> BIOME_RENAMES = new HashMap<>(
 +            ImmutableMap.<String, String>builder()
@@ -15225,12 +17243,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                    .build()
 +    );
 +
-+
-+    private V2553() {}
-+
 +    public static void register() {
 +        ConverterAbstractStringValueTypeRename.register(VERSION, MCTypeRegistry.BIOME, BIOME_RENAMES::get);
 +    }
++
++    private V2553() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2558.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2558.java
 new file mode 100644
@@ -15247,16 +17264,17 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.types.MapType;
 +import ca.spottedleaf.dataconverter.types.Types;
 +import com.google.common.collect.ImmutableMap;
++import java.util.HashMap;
 +
 +public final class V2558 {
 +
-+    protected static final int VERSION = MCVersions.V1_16_PRE2 + 1;
-+
-+    private V2558() {}
++    private static final int VERSION = MCVersions.V1_16_PRE2 + 1;
 +
 +    public static void register() {
-+        ConverterAbstractOptionsRename.register(VERSION, ImmutableMap.of(
-+                "key_key.swapHands", "key_key.swapOffhand"
++        ConverterAbstractOptionsRename.register(VERSION, new HashMap<>(
++                ImmutableMap.of(
++                        "key_key.swapHands", "key_key.swapOffhand"
++                )
 +        )::get);
 +
 +        MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureConverter(new DataConverter<>(VERSION) {
@@ -15282,6 +17300,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +        return V2550.vanillaLevels(seed, V2550.defaultOverworld(seed), false);
 +    }
++
++    private V2558() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2568.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2568.java
 new file mode 100644
@@ -15292,22 +17312,20 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
-+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
-+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists;
 +
 +public final class V2568 {
 +
-+    protected static final int VERSION = MCVersions.V1_16_1 + 1;
-+
-+    private V2568() {}
++    private static final int VERSION = MCVersions.V1_16_1 + 1;
 +
 +    private static void registerMob(final String id) {
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems"));
++        V100.registerEquipment(VERSION, id);
 +    }
 +
 +    public static void register() {
 +        registerMob("minecraft:piglin_brute");
 +    }
++
++    private V2568() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2671.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2671.java
 new file mode 100644
@@ -15318,20 +17336,20 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
-+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
-+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists;
 +
 +public final class V2671 {
 +
-+    protected static final int VERSION = MCVersions.V1_16_5 + 85;
++    private static final int VERSION = MCVersions.V1_16_5 + 85;
 +
 +    private static void registerMob(final String id) {
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems"));
++        V100.registerEquipment(VERSION, id);
 +    }
 +
 +    public static void register() {
 +        registerMob("minecraft:goat");
 +    }
++
++    private V2671() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2679.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2679.java
 new file mode 100644
@@ -15348,7 +17366,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V2679 {
 +
-+    protected static final int VERSION = MCVersions.V1_16_5 + 93;
++    private static final int VERSION = MCVersions.V1_16_5 + 93;
 +
 +    public static void register() {
 +        MCTypeRegistry.BLOCK_STATE.addStructureConverter(new DataConverter<>(VERSION) {
@@ -15374,6 +17392,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            }
 +        });
 +    }
++
++    private V2679() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2680.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2680.java
 new file mode 100644
@@ -15387,20 +17407,26 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename;
 +import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename;
 +import com.google.common.collect.ImmutableMap;
++import java.util.HashMap;
 +
 +public final class V2680 {
 +
-+    protected static final int VERSION = MCVersions.V1_16_5 + 94;
++    private static final int VERSION = MCVersions.V1_16_5 + 94;
 +
 +    public static void register() {
-+        ConverterAbstractItemRename.register(VERSION, ImmutableMap.of(
-+                "minecraft:grass_path", "minecraft:dirt_path"
++        ConverterAbstractItemRename.register(VERSION, new HashMap<>(
++                ImmutableMap.of(
++                        "minecraft:grass_path", "minecraft:dirt_path"
++                )
 +        )::get);
-+        ConverterAbstractBlockRename.registerAndFixJigsaw(VERSION, ImmutableMap.of(
-+                "minecraft:grass_path", "minecraft:dirt_path"
++        ConverterAbstractBlockRename.register(VERSION, new HashMap<>(
++                ImmutableMap.of(
++                        "minecraft:grass_path", "minecraft:dirt_path"
++                )
 +        )::get);
 +    }
 +
++    private V2680() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2684.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2684.java
 new file mode 100644
@@ -15416,11 +17442,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V2684 {
 +
-+    protected static final int VERSION = MCVersions.V20W48A + 1;
++    private static final int VERSION = MCVersions.V20W48A + 1;
 +
 +    public static void register() {
 +        MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:sculk_sensor", new GameEventListenerWalker());
 +    }
++
++    private V2684() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2686.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2686.java
 new file mode 100644
@@ -15431,20 +17459,20 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
-+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
-+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists;
 +
 +public final class V2686 {
 +
-+    protected static final int VERSION = MCVersions.V20W49A + 1;
++    private static final int VERSION = MCVersions.V20W49A + 1;
 +
 +    private static void registerMob(final String id) {
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems"));
++        V100.registerEquipment(VERSION, id);
 +    }
 +
 +    public static void register() {
 +        registerMob("minecraft:axolotl");
 +    }
++
++    private V2686() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2688.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2688.java
 new file mode 100644
@@ -15461,16 +17489,18 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V2688 {
 +
-+    protected static final int VERSION = MCVersions.V20W51A + 1;
++    private static final int VERSION = MCVersions.V20W51A + 1;
 +
 +    private static void registerMob(final String id) {
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems"));
++        V100.registerEquipment(VERSION, id);
 +    }
 +
 +    public static void register() {
 +        registerMob("minecraft:glow_squid");
 +        MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:glow_item_frame", new DataWalkerItems("Item"));
 +    }
++
++    private V2688() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2690.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2690.java
 new file mode 100644
@@ -15489,9 +17519,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V2690 {
 +
-+    protected static final int VERSION = MCVersions.V21W05A;
++    private static final int VERSION = MCVersions.V21W05A;
 +
-+    protected static final Map<String, String> RENAMES = new HashMap<>(
++    private static final Map<String, String> RENAMES = new HashMap<>(
 +            ImmutableMap.<String, String>builder()
 +                    .put("minecraft:weathered_copper_block", "minecraft:oxidized_copper_block")
 +                    .put("minecraft:semi_weathered_copper_block", "minecraft:weathered_copper_block")
@@ -15518,8 +17548,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +    public static void register() {
 +        ConverterAbstractItemRename.register(VERSION, RENAMES::get);
-+        ConverterAbstractBlockRename.registerAndFixJigsaw(VERSION, RENAMES::get);
++        ConverterAbstractBlockRename.register(VERSION, RENAMES::get);
 +    }
++
++    private V2690() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2691.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2691.java
 new file mode 100644
@@ -15538,9 +17570,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V2691 {
 +
-+    protected static final int VERSION = MCVersions.V21W05A + 1;
++    private static final int VERSION = MCVersions.V21W05A + 1;
 +
-+    protected static final Map<String, String> RENAMES = new HashMap<>(
++    private static final Map<String, String> RENAMES = new HashMap<>(
 +            ImmutableMap.<String, String>builder()
 +                    .put("minecraft:waxed_copper", "minecraft:waxed_copper_block")
 +                    .put("minecraft:oxidized_copper_block", "minecraft:oxidized_copper")
@@ -15551,8 +17583,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +    public static void register() {
 +        ConverterAbstractItemRename.register(VERSION, RENAMES::get);
-+        ConverterAbstractBlockRename.registerAndFixJigsaw(VERSION, RENAMES::get);
++        ConverterAbstractBlockRename.register(VERSION, RENAMES::get);
 +    }
++
++    private V2691() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2693.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2693.java
 new file mode 100644
@@ -15568,12 +17602,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V2693 {
 +
-+    protected static final int VERSION = MCVersions.V21W05B + 1;
++    private static final int VERSION = MCVersions.V21W05B + 1;
 +
 +    public static void register() {
 +        MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureConverter(new AddFlagIfAbsent(VERSION, "has_increased_height_already", false));
 +    }
 +
++    private V2693() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2696.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2696.java
 new file mode 100644
@@ -15592,9 +17627,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V2696 {
 +
-+    protected static final int VERSION = MCVersions.V21W07A + 1;
++    private static final int VERSION = MCVersions.V21W07A + 1;
 +
-+    protected static final Map<String, String> RENAMES = new HashMap<>(
++    private static final Map<String, String> RENAMES = new HashMap<>(
 +            ImmutableMap.<String, String>builder()
 +                    .put("minecraft:grimstone", "minecraft:deepslate")
 +                    .put("minecraft:grimstone_slab", "minecraft:cobbled_deepslate_slab")
@@ -15618,8 +17653,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +    public static void register() {
 +        ConverterAbstractItemRename.register(VERSION, RENAMES::get);
-+        ConverterAbstractBlockRename.registerAndFixJigsaw(VERSION, RENAMES::get);
++        ConverterAbstractBlockRename.register(VERSION, RENAMES::get);
 +    }
++
++    private V2696() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2700.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2700.java
 new file mode 100644
@@ -15632,17 +17669,22 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
 +import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename;
 +import com.google.common.collect.ImmutableMap;
++import java.util.HashMap;
 +
 +public final class V2700 {
 +
-+    protected static final int VERSION = MCVersions.V21W10A + 1;
++    private static final int VERSION = MCVersions.V21W10A + 1;
 +
 +    public static void register() {
-+        ConverterAbstractBlockRename.registerAndFixJigsaw(VERSION, ImmutableMap.of(
-+                "minecraft:cave_vines_head", "minecraft:cave_vines",
-+                "minecraft:cave_vines_body", "minecraft:cave_vines_plant"
++        ConverterAbstractBlockRename.register(VERSION, new HashMap<>(
++                ImmutableMap.of(
++                        "minecraft:cave_vines_head", "minecraft:cave_vines",
++                        "minecraft:cave_vines_body", "minecraft:cave_vines_plant"
++                )
 +        )::get);
 +    }
++
++    private V2700() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2701.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2701.java
 new file mode 100644
@@ -15665,7 +17707,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V2701 {
 +
-+    protected static final int VERSION = MCVersions.V21W10A + 2;
++    private static final int VERSION = MCVersions.V21W10A + 2;
 +
 +    private static final Pattern INDEX_PATTERN = Pattern.compile("\\[(\\d+)\\]");
 +
@@ -15759,7 +17801,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        return current instanceof String ? (String)current : "";
 +    }
 +
-+    protected static String convertToString(final MapType<String> feature) {
++    private static String convertToString(final MapType<String> feature) {
 +        return getReplacement(
 +                getNestedString(feature, "type"),
 +                getNestedString(feature, "name"),
@@ -15852,6 +17894,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +        return null;
 +    }
++
++    private V2701() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2702.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2702.java
 new file mode 100644
@@ -15868,7 +17912,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V2702 {
 +
-+    protected static final int VERSION = MCVersions.V21W10A + 3;
++    private static final int VERSION = MCVersions.V21W10A + 3;
 +
 +    public static void register() {
 +        final DataConverter<MapType<String>, MapType<String>> arrowConverter = new DataConverter<>(VERSION) {
@@ -15891,6 +17935,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        MCTypeRegistry.ENTITY.addConverterForId("minecraft:spectral_arrow", arrowConverter);
 +        MCTypeRegistry.ENTITY.addConverterForId("minecraft:trident", arrowConverter);
 +    }
++
++    private V2702() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2707.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2707.java
 new file mode 100644
@@ -15903,14 +17949,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
 +import ca.spottedleaf.dataconverter.minecraft.converters.helpers.AddFlagIfAbsent;
 +import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
-+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists;
 +
 +public final class V2707 {
 +
-+    protected static final int VERSION = MCVersions.V21W14A + 1;
++    private static final int VERSION = MCVersions.V21W14A + 1;
 +
 +    private static void registerMob(final String id) {
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems"));
++        V100.registerEquipment(VERSION, id);
 +    }
 +
 +    public static void register() {
@@ -15918,6 +17963,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +        registerMob("minecraft:marker"); // ?????????????
 +    }
++
++    private V2707() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2710.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2710.java
 new file mode 100644
@@ -15930,17 +17977,21 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
 +import ca.spottedleaf.dataconverter.minecraft.converters.stats.ConverterAbstractStatsRename;
 +import com.google.common.collect.ImmutableMap;
++import java.util.HashMap;
 +
 +public final class V2710 {
 +
-+    protected static final int VERSION = MCVersions.V21W15A + 1;
++    private static final int VERSION = MCVersions.V21W15A + 1;
 +
 +    public static void register() {
-+        ConverterAbstractStatsRename.register(VERSION, ImmutableMap.of(
-+                "minecraft:play_one_minute", "minecraft:play_time"
++        ConverterAbstractStatsRename.register(VERSION, new HashMap<>(
++                ImmutableMap.of(
++                        "minecraft:play_one_minute", "minecraft:play_time"
++                )
 +        )::get);
 +    }
 +
++    private V2710() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2717.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2717.java
 new file mode 100644
@@ -15954,19 +18005,24 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename;
 +import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename;
 +import com.google.common.collect.ImmutableMap;
++import java.util.HashMap;
++import java.util.Map;
 +
 +public final class V2717 {
 +
-+    protected static final int VERSION = MCVersions.V1_17_PRE1 + 1;
++    private static final int VERSION = MCVersions.V1_17_PRE1 + 1;
 +
 +    public static void register() {
-+        final ImmutableMap<String, String> rename = ImmutableMap.of(
-+                "minecraft:azalea_leaves_flowers", "minecraft:flowering_azalea_leaves"
++        final Map<String, String> rename = new HashMap<>(
++                ImmutableMap.of(
++                        "minecraft:azalea_leaves_flowers", "minecraft:flowering_azalea_leaves"
++                )
 +        );
 +        ConverterAbstractItemRename.register(VERSION, rename::get);
 +        ConverterAbstractBlockRename.register(VERSION, rename::get);
 +    }
 +
++    private V2717() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2825.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2825.java
 new file mode 100644
@@ -15982,12 +18038,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V2825 {
 +
-+    protected static final int VERSION = MCVersions.V1_17_1 + 95;
++    private static final int VERSION = MCVersions.V1_17_1 + 95;
 +
 +    public static void register() {
 +        MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureConverter(new AddFlagIfAbsent(VERSION, "has_increased_height_already", false));
 +    }
 +
++    private V2825() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2831.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2831.java
 new file mode 100644
@@ -16008,7 +18065,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V2831 {
 +
-+    protected static final int VERSION = MCVersions.V1_17_1 + 101;
++    private static final int VERSION = MCVersions.V1_17_1 + 101;
 +
 +    public static void register() {
 +        MCTypeRegistry.UNTAGGED_SPAWNER.addStructureWalker(VERSION, (final MapType<String> root, final long fromVersion, final long toVersion) -> {
@@ -16063,6 +18120,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            }
 +        });
 +    }
++
++    private V2831() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2832.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2832.java
 new file mode 100644
@@ -16096,7 +18155,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +    private static final Logger LOGGER = LogUtils.getLogger();
 +
-+    protected static final int VERSION = MCVersions.V1_17_1 + 102;
++    private static final int VERSION = MCVersions.V1_17_1 + 102;
 +
 +    private static final String[] BIOMES_BY_ID = new String[256]; // rip datapacks
 +    static {
@@ -16996,6 +19055,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +        return ret;
 +    }
++
++    private V2832() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2833.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2833.java
 new file mode 100644
@@ -17012,7 +19073,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V2833 {
 +
-+    protected static final int VERSION = MCVersions.V1_17_1 + 103;
++    private static final int VERSION = MCVersions.V1_17_1 + 103;
 +
 +    public static void register() {
 +        MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureConverter(new DataConverter<>(VERSION) {
@@ -17032,6 +19093,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        });
 +    }
 +
++    private V2833() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2838.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2838.java
 new file mode 100644
@@ -17050,7 +19112,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V2838 {
 +
-+    protected static final int VERSION = MCVersions.V21W40A;
++    private static final int VERSION = MCVersions.V21W40A;
 +
 +    public static final Map<String, String> BIOME_UPDATE = new HashMap<>(
 +            ImmutableMap.<String, String>builder()
@@ -17098,6 +19160,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    public static void register() {
 +        ConverterAbstractStringValueTypeRename.register(VERSION, MCTypeRegistry.BIOME, BIOME_UPDATE::get);
 +    }
++
++    private V2838() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2841.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2841.java
 new file mode 100644
@@ -17122,15 +19186,17 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V2841 {
 +
-+    protected static final int VERSION = MCVersions.V21W42A + 1;
++    private static final int VERSION = MCVersions.V21W42A + 1;
 +
-+    protected static final Set<String> ALWAYS_WATERLOGGED = new HashSet<>(Arrays.asList(
-+            "minecraft:bubble_column",
-+            "minecraft:kelp",
-+            "minecraft:kelp_plant",
-+            "minecraft:seagrass",
-+            "minecraft:tall_seagrass"
-+    ));
++    private static final Set<String> ALWAYS_WATERLOGGED = new HashSet<>(
++            Arrays.asList(
++                    "minecraft:bubble_column",
++                    "minecraft:kelp",
++                    "minecraft:kelp_plant",
++                    "minecraft:seagrass",
++                    "minecraft:tall_seagrass"
++            )
++    );
 +
 +    public static void register() {
 +        MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) {
@@ -17250,15 +19316,18 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        }
 +
 +        final MapType<String> properties = blockState.getMap("Properties");
++        // Correctly read block state properties as strings - https://github.com/PaperMC/DataConverter/issues/6
 +        if ("minecraft:water".equals(name)) {
-+            return properties != null && properties.getInt("level") == 0 ? "minecraft:water" : "minecraft:flowing_water";
++            return properties != null && "0".equals(properties.getString("level")) ? "minecraft:water" : "minecraft:flowing_water";
 +        } else if ("minecraft:lava".equals(name)) {
-+            return properties != null && properties.getInt("level") == 0 ? "minecraft:lava" : "minecraft:flowing_lava";
++            return properties != null && "0".equals(properties.getString("level")) ? "minecraft:lava" : "minecraft:flowing_lava";
 +        }
 +
-+        return (properties != null && properties.getBoolean("waterlogged")) ? "minecraft:water" : "minecraft:empty";
++        return (properties != null && "true".equals(properties.getString("waterlogged"))) ? "minecraft:water" : "minecraft:empty";
 +    }
 +
++    private V2841() {}
++
 +    public static final class SimplePaletteReader {
 +
 +        public final ListType palette;
@@ -17329,7 +19398,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V2842 {
 +
-+    protected static final int VERSION = MCVersions.V21W42A + 2;
++    private static final int VERSION = MCVersions.V21W42A + 2;
 +
 +    public static void register() {
 +        MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) {
@@ -17341,7 +19410,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                if (!root.isEmpty()) {
 +                    for (final String key : root.keys()) {
 +                        if (level.hasKey(key)) {
-+                            // Don't clobber level's data
++                            // Don't clobber Level's data
 +                            continue;
 +                        }
 +                        level.setGeneric(key, root.getGeneric(key));
@@ -17391,6 +19460,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            return null;
 +        });
 +    }
++
++    private V2842() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2843.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2843.java
 new file mode 100644
@@ -17409,14 +19480,19 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.types.MapType;
 +import ca.spottedleaf.dataconverter.types.ObjectType;
 +import ca.spottedleaf.dataconverter.types.Types;
++import java.util.HashMap;
 +import java.util.Map;
 +
 +public final class V2843 {
 +
-+    protected static final int VERSION = MCVersions.V21W42A + 3;
++    private static final int VERSION = MCVersions.V21W42A + 3;
 +
 +    public static void register() {
-+        ConverterAbstractStringValueTypeRename.register(VERSION, MCTypeRegistry.BIOME, Map.of("minecraft:deep_warm_ocean", "minecraft:warm_ocean")::get);
++        ConverterAbstractStringValueTypeRename.register(VERSION, MCTypeRegistry.BIOME,
++                new HashMap<>(
++                        Map.of("minecraft:deep_warm_ocean", "minecraft:warm_ocean")
++                )::get
++        );
 +
 +        MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) {
 +            private static void moveOutOfBoundTicks(final ListType ticks, final MapType<String> chunkRoot, final int chunkX, final int chunkZ, final String intoKey) {
@@ -17500,8 +19576,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +            return null;
 +        });
-+
 +    }
++
++    private V2843() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2846.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2846.java
 new file mode 100644
@@ -17514,18 +19591,23 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
 +import ca.spottedleaf.dataconverter.minecraft.converters.advancements.ConverterAbstractAdvancementsRename;
 +import com.google.common.collect.ImmutableMap;
++import java.util.HashMap;
 +
 +public final class V2846 {
 +
-+    protected static final int VERSION = MCVersions.V21W44A + 1;
++    private static final int VERSION = MCVersions.V21W44A + 1;
 +
 +    public static void register() {
-+        ConverterAbstractAdvancementsRename.register(VERSION, ImmutableMap.of(
-+                "minecraft:husbandry/play_jukebox_in_meadows", "minecraft:adventure/play_jukebox_in_meadows",
-+                "minecraft:adventure/caves_and_cliff", "minecraft:adventure/fall_from_world_height",
-+                "minecraft:adventure/ride_strider_in_overworld_lava", "minecraft:nether/ride_strider_in_overworld_lava"
++        ConverterAbstractAdvancementsRename.register(VERSION, new HashMap<>(
++                ImmutableMap.of(
++                        "minecraft:husbandry/play_jukebox_in_meadows", "minecraft:adventure/play_jukebox_in_meadows",
++                        "minecraft:adventure/caves_and_cliff", "minecraft:adventure/fall_from_world_height",
++                        "minecraft:adventure/ride_strider_in_overworld_lava", "minecraft:nether/ride_strider_in_overworld_lava"
++                )
 +        )::get);
 +    }
++
++    private V2846() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2852.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2852.java
 new file mode 100644
@@ -17542,7 +19624,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V2852 {
 +
-+    protected static final int VERSION = MCVersions.V1_18_PRE5 + 1;
++    private static final int VERSION = MCVersions.V1_18_PRE5 + 1;
 +
 +    public static void register() {
 +        MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureConverter(new DataConverter<>(VERSION) {
@@ -17561,6 +19643,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            }
 +        });
 +    }
++
++    private V2852() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2967.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2967.java
 new file mode 100644
@@ -17577,7 +19661,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V2967 {
 +
-+    protected static final int VERSION = MCVersions.V22W05A;
++    private static final int VERSION = MCVersions.V22W05A;
 +
 +    public static void register() {
 +        MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureConverter(new DataConverter<>(VERSION) {
@@ -17623,6 +19707,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            }
 +        });
 +    }
++
++    private V2967() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2970.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2970.java
 new file mode 100644
@@ -17638,10 +19724,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.types.ListType;
 +import ca.spottedleaf.dataconverter.types.MapType;
 +import ca.spottedleaf.dataconverter.types.ObjectType;
-+import ca.spottedleaf.dataconverter.types.Types;
 +import com.google.common.collect.ImmutableMap;
++import com.mojang.logging.LogUtils;
 +import it.unimi.dsi.fastutil.objects.Object2IntMap;
 +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
++import org.slf4j.Logger;
 +import java.util.HashMap;
 +import java.util.Iterator;
 +import java.util.List;
@@ -17650,7 +19737,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V2970 {
 +
-+    protected static final int VERSION = MCVersions.V22W07A + 1;
++    private static final Logger LOGGER = LogUtils.getLogger();
++
++    private static final int VERSION = MCVersions.V22W07A + 1;
 +    private static final Map<String, BiomeRemap> CONVERSION_MAP = new HashMap<>(
 +            ImmutableMap.<String, BiomeRemap>builder()
 +                    .put("mineshaft", BiomeRemap.create(Map.of(List.of("minecraft:badlands", "minecraft:eroded_badlands", "minecraft:wooded_badlands"), "minecraft:mineshaft_mesa"), "minecraft:mineshaft"))
@@ -17711,7 +19800,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                id = id.toLowerCase(Locale.ROOT);
 +                final BiomeRemap remap = CONVERSION_MAP.get(id);
 +                if (remap == null) {
-+                    throw new IllegalArgumentException("Unknown structure " + id);
++                    return null;
 +                }
 +                if (remap.biomeToNewStructure == null || biomeCount == null) {
 +                    return remap.dfl;
@@ -17755,7 +19844,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                final MapType<String> references = structures.getMap("References");
 +
 +                if (starts != null) {
-+                    final MapType<String> newStarts = Types.NBT.createEmptyMap();
++                    final MapType<String> newStarts = data.getTypeUtil().createEmptyMap();
 +                    structures.setMap("starts", newStarts);
 +
 +                    for (final String key : starts.keys()) {
@@ -17765,6 +19854,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                        }
 +
 +                        final String remapped = getStructureConverted(key, biomeCounts);
++
++                        if (remapped == null) {
++                            LOGGER.warn("Encountered unknown structure in dataconverter: " + key);
++                            continue;
++                        }
++
 +                        value.setString("id", remapped);
 +                        newStarts.setMap(remapped, value);
 +                    }
@@ -17772,7 +19867,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +                // This TRULY is a guess, no idea what biomes the referent has.
 +                if (references != null) {
-+                    final MapType<String> newReferences = Types.NBT.createEmptyMap();
++                    final MapType<String> newReferences = data.getTypeUtil().createEmptyMap();
 +                    structures.setMap("References", newReferences);
 +                    for (final String key : references.keys()) {
 +                        final long[] value = references.getLongs(key);
@@ -17780,7 +19875,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                            continue;
 +                        }
 +
-+                        newReferences.setLongs(getStructureConverted(key, biomeCounts), value);
++                        final String newKey = getStructureConverted(key, biomeCounts);
++                        if (newKey == null) {
++                            LOGGER.warn("Encountered unknown structure reference in dataconverter: " + key);
++                            continue;
++                        }
++
++                        newReferences.setLongs(newKey, value);
 +                    }
 +                }
 +
@@ -17789,6 +19890,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        });
 +    }
 +
++    private V2970() {}
++
 +    private static final class BiomeRemap {
 +
 +        public final Map<String, String> biomeToNewStructure;
@@ -17839,7 +19942,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V3077 {
 +
-+    protected static final int VERSION = MCVersions.V1_18_2 + 102;
++    private static final int VERSION = MCVersions.V1_18_2 + 102;
 +
 +    public static void register() {
 +        MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) {
@@ -17865,6 +19968,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            }
 +        });
 +    }
++
++    private V3077() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3078.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3078.java
 new file mode 100644
@@ -17881,10 +19986,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V3078 {
 +
-+    protected static final int VERSION = MCVersions.V1_18_2 + 103;
++    private static final int VERSION = MCVersions.V1_18_2 + 103;
 +
 +    private static void registerMob(final String id) {
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems"));
++        V100.registerEquipment(VERSION, id);
 +    }
 +
 +    public static void register() {
@@ -17892,6 +19997,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        registerMob("minecraft:tadpole");
 +        MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:sculk_shrieker", new GameEventListenerWalker());
 +    }
++
++    private V3078() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3081.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3081.java
 new file mode 100644
@@ -17904,20 +20011,21 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
 +import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
 +import ca.spottedleaf.dataconverter.minecraft.walkers.game_event.GameEventListenerWalker;
-+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists;
 +
 +public final class V3081 {
 +
-+    protected static final int VERSION = MCVersions.V22W11A + 1;
++    private static final int VERSION = MCVersions.V22W11A + 1;
 +
 +    private static void registerMob(final String id) {
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems"));
++        V100.registerEquipment(VERSION, id);
 +    }
 +
 +    public static void register() {
 +        registerMob("minecraft:warden");
 +        MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:warden", new GameEventListenerWalker());
 +    }
++
++    private V3081() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3082.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3082.java
 new file mode 100644
@@ -17933,11 +20041,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V3082 {
 +
-+    protected static final int VERSION = MCVersions.V22W11A + 2;
++    private static final int VERSION = MCVersions.V22W11A + 2;
 +
 +    public static void register() {
 +        MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:chest_boat", new DataWalkerItemLists("Items"));
 +    }
++
++    private V3082() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3083.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3083.java
 new file mode 100644
@@ -17954,16 +20064,19 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V3083 {
 +
-+    protected static final int VERSION = MCVersions.V22W12A + 1;
++    private static final int VERSION = MCVersions.V22W12A + 1;
 +
 +    private static void registerMob(final String id) {
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems"));
++        V100.registerEquipment(VERSION, id);
 +    }
 +
 +    public static void register() {
 +        registerMob("minecraft:allay");
++        MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:allay", new DataWalkerItemLists("Inventory"));
 +        MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:allay", new GameEventListenerWalker());
 +    }
++
++    private V3083() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3084.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3084.java
 new file mode 100644
@@ -17983,25 +20096,26 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V3084 {
 +
-+    protected static final int VERSION = MCVersions.V22W12A + 2;
++    private static final int VERSION = MCVersions.V22W12A + 2;
 +
-+    protected static final Map<String, String> GAME_EVENT_RENAMES = new HashMap<>(ImmutableMap.<String, String>builder()
-+            .put("minecraft:block_press", "minecraft:block_activate")
-+            .put("minecraft:block_switch", "minecraft:block_activate")
-+            .put("minecraft:block_unpress", "minecraft:block_deactivate")
-+            .put("minecraft:block_unswitch", "minecraft:block_deactivate")
-+            .put("minecraft:drinking_finish", "minecraft:drink")
-+            .put("minecraft:elytra_free_fall", "minecraft:elytra_glide")
-+            .put("minecraft:entity_damaged", "minecraft:entity_damage")
-+            .put("minecraft:entity_dying", "minecraft:entity_die")
-+            .put("minecraft:entity_killed", "minecraft:entity_die")
-+            .put("minecraft:mob_interact", "minecraft:entity_interact")
-+            .put("minecraft:ravager_roar", "minecraft:entity_roar")
-+            .put("minecraft:ring_bell", "minecraft:block_change")
-+            .put("minecraft:shulker_close", "minecraft:container_close")
-+            .put("minecraft:shulker_open", "minecraft:container_open")
-+            .put("minecraft:wolf_shaking", "minecraft:entity_shake")
-+            .build()
++    private static final Map<String, String> GAME_EVENT_RENAMES = new HashMap<>(
++            ImmutableMap.<String, String>builder()
++                    .put("minecraft:block_press", "minecraft:block_activate")
++                    .put("minecraft:block_switch", "minecraft:block_activate")
++                    .put("minecraft:block_unpress", "minecraft:block_deactivate")
++                    .put("minecraft:block_unswitch", "minecraft:block_deactivate")
++                    .put("minecraft:drinking_finish", "minecraft:drink")
++                    .put("minecraft:elytra_free_fall", "minecraft:elytra_glide")
++                    .put("minecraft:entity_damaged", "minecraft:entity_damage")
++                    .put("minecraft:entity_dying", "minecraft:entity_die")
++                    .put("minecraft:entity_killed", "minecraft:entity_die")
++                    .put("minecraft:mob_interact", "minecraft:entity_interact")
++                    .put("minecraft:ravager_roar", "minecraft:entity_roar")
++                    .put("minecraft:ring_bell", "minecraft:block_change")
++                    .put("minecraft:shulker_close", "minecraft:container_close")
++                    .put("minecraft:shulker_open", "minecraft:container_open")
++                    .put("minecraft:wolf_shaking", "minecraft:entity_shake")
++                    .build()
 +    );
 +
 +    public static void register() {
@@ -18009,6 +20123,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            return GAME_EVENT_RENAMES.get(NamespaceUtil.correctNamespace(name));
 +        });
 +    }
++
++    private V3084() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3086.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3086.java
 new file mode 100644
@@ -18029,9 +20145,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V3086 {
 +
-+    protected static final int VERSION = MCVersions.V22W13A + 1;
++    private static final int VERSION = MCVersions.V22W13A + 1;
 +
-+    protected static final Int2ObjectOpenHashMap<String> CAT_ID_CONVERSION = new Int2ObjectOpenHashMap<>();
++    private static final Int2ObjectOpenHashMap<String> CAT_ID_CONVERSION = new Int2ObjectOpenHashMap<>();
 +    static {
 +        CAT_ID_CONVERSION.defaultReturnValue("minecraft:tabby");
 +        CAT_ID_CONVERSION.put(0, "minecraft:tabby");
@@ -18047,25 +20163,28 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        CAT_ID_CONVERSION.put(10, "minecraft:all_black");
 +    }
 +
-+    protected static final Map<String, String> CAT_ADVANCEMENTS_CONVERSION = new HashMap<>(ImmutableMap.<String, String>builder()
-+            .put("textures/entity/cat/tabby.png", "minecraft:tabby")
-+            .put("textures/entity/cat/black.png", "minecraft:black")
-+            .put("textures/entity/cat/red.png", "minecraft:red")
-+            .put("textures/entity/cat/siamese.png", "minecraft:siamese")
-+            .put("textures/entity/cat/british_shorthair.png", "minecraft:british")
-+            .put("textures/entity/cat/calico.png", "minecraft:calico")
-+            .put("textures/entity/cat/persian.png", "minecraft:persian")
-+            .put("textures/entity/cat/ragdoll.png", "minecraft:ragdoll")
-+            .put("textures/entity/cat/white.png", "minecraft:white")
-+            .put("textures/entity/cat/jellie.png", "minecraft:jellie")
-+            .put("textures/entity/cat/all_black.png", "minecraft:all_black")
-+            .build()
++    private static final Map<String, String> CAT_ADVANCEMENTS_CONVERSION = new HashMap<>(
++            ImmutableMap.<String, String>builder()
++                    .put("textures/entity/cat/tabby.png", "minecraft:tabby")
++                    .put("textures/entity/cat/black.png", "minecraft:black")
++                    .put("textures/entity/cat/red.png", "minecraft:red")
++                    .put("textures/entity/cat/siamese.png", "minecraft:siamese")
++                    .put("textures/entity/cat/british_shorthair.png", "minecraft:british")
++                    .put("textures/entity/cat/calico.png", "minecraft:calico")
++                    .put("textures/entity/cat/persian.png", "minecraft:persian")
++                    .put("textures/entity/cat/ragdoll.png", "minecraft:ragdoll")
++                    .put("textures/entity/cat/white.png", "minecraft:white")
++                    .put("textures/entity/cat/jellie.png", "minecraft:jellie")
++                    .put("textures/entity/cat/all_black.png", "minecraft:all_black")
++                    .build()
 +    );
 +
 +    public static void register() {
 +        MCTypeRegistry.ENTITY.addConverterForId("minecraft:cat", new ConverterEntityToVariant(VERSION, "CatType", CAT_ID_CONVERSION::get));
 +        MCTypeRegistry.ADVANCEMENTS.addStructureConverter(new ConverterCriteriaRename(VERSION, "minecraft:husbandry/complete_catalogue", CAT_ADVANCEMENTS_CONVERSION::get));
 +    }
++
++    private V3086() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3087.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3087.java
 new file mode 100644
@@ -18082,9 +20201,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V3087 {
 +
-+    protected static final int VERSION = MCVersions.V22W13A + 2;
++    private static final int VERSION = MCVersions.V22W13A + 2;
 +
-+    protected static Int2ObjectOpenHashMap<String> FROG_ID_CONVERSION = new Int2ObjectOpenHashMap<>();
++    private static final Int2ObjectOpenHashMap<String> FROG_ID_CONVERSION = new Int2ObjectOpenHashMap<>();
 +    static {
 +        FROG_ID_CONVERSION.put(0, "minecraft:temperate");
 +        FROG_ID_CONVERSION.put(1, "minecraft:warm");
@@ -18094,6 +20213,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    public static void register() {
 +        MCTypeRegistry.ENTITY.addConverterForId("minecraft:frog", new ConverterEntityToVariant(VERSION, "Variant", FROG_ID_CONVERSION::get));
 +    }
++
++    private V3087() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3088.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3088.java
 new file mode 100644
@@ -18118,11 +20239,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    // players load the chunk - they went through a different conversion process, which ultimately creates two versions.
 +    // Unfortunately this fix doesn't exactly resolve it, as anyone running Mojang's converters will now be different
 +    // from DataConverter's. It's broadly a dumb situation all around that could be avoided if Mojang wasn't being careless here.
-+    protected static final int VERSION = MCVersions.V22W14A;
++    private static final int VERSION = MCVersions.V22W14A;
 +
 +    public static void register() {
 +        MCTypeRegistry.CHUNK.addStructureConverter(new ConverterAddBlendingData(VERSION));
 +    }
++
++    private V3088() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3090.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3090.java
 new file mode 100644
@@ -18140,7 +20263,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V3090 {
 +
-+    protected static final int VERSION = MCVersions.V22W15A + 1;
++    private static final int VERSION = MCVersions.V22W15A + 1;
 +
 +    public static void register() {
 +        MCTypeRegistry.ENTITY.addConverterForId("minecraft:painting", new DataConverter<>(VERSION) {
@@ -18152,6 +20275,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            }
 +        });
 +    }
++
++    private V3090() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3093.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3093.java
 new file mode 100644
@@ -18168,7 +20293,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V3093 {
 +
-+    protected static final int VERSION = MCVersions.V22W17A;
++    private static final int VERSION = MCVersions.V22W17A;
 +
 +    public static void register() {
 +        MCTypeRegistry.ENTITY.addConverterForId("minecraft:goat", new DataConverter<>(VERSION) {
@@ -18180,6 +20305,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            }
 +        });
 +    }
++
++    private V3093() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3094.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3094.java
 new file mode 100644
@@ -18196,7 +20323,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V3094 {
 +
-+    public static final int VERSION = MCVersions.V22W17A + 1;
++    private static final int VERSION = MCVersions.V22W17A + 1;
 +
 +    private static final String[] SOUND_VARIANT_TO_INSTRUMENT = new String[] {
 +            "minecraft:ponder_goat_horn",
@@ -18228,6 +20355,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            }
 +        });
 +    }
++
++    private V3094() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3097.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3097.java
 new file mode 100644
@@ -18295,6 +20424,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        MCTypeRegistry.ADVANCEMENTS.addStructureConverter(new ConverterCriteriaRename(VERSION, "minecraft:husbandry/complete_catalogue", britishRenamer::get));
 +        MCTypeRegistry.POI_CHUNK.addStructureConverter(new ConverterPoiDelete(VERSION, poiRemove::contains));
 +    }
++
++    private V3097() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3108.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3108.java
 new file mode 100644
@@ -18328,6 +20459,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            }
 +        });
 +    }
++
++    private V3108() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3201.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3201.java
 new file mode 100644
@@ -18367,6 +20500,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            }
 +        });
 +    }
++
++    private V3201() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3203.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3203.java
 new file mode 100644
@@ -18385,12 +20520,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    private static final int VERSION = MCVersions.V1_19_2 + 83;
 +
 +    private static void registerMob(final String id) {
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems"));
++        V100.registerEquipment(VERSION, id);
 +    }
 +
 +    public static void register() {
 +        registerMob("minecraft:camel");
 +    }
++
++    private V3203() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3204.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3204.java
 new file mode 100644
@@ -18411,6 +20548,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    public static void register() {
 +        MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:chiseled_bookshelf", new DataWalkerItemLists("Items"));
 +    }
++
++    private V3204() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3209.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3209.java
 new file mode 100644
@@ -18433,6 +20572,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        // remapped this version
 +        MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:pig_spawn_egg", new ConverterFlattenSpawnEgg(VERSION, 0));
 +    }
++
++    private V3209() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3214.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3214.java
 new file mode 100644
@@ -18467,6 +20608,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            }
 +        });
 +    }
++
++    private V3214() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3319.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3319.java
 new file mode 100644
@@ -18494,6 +20637,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            }
 +        });
 +    }
++
++    private V3319() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3322.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3322.java
 new file mode 100644
@@ -18582,6 +20727,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            }
 +        });
 +    }
++
++    private V3322() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3325.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3325.java
 new file mode 100644
@@ -18605,6 +20752,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:block_display", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "block_state"));
 +        // text_display is a simple entity
 +    }
++
++    private V3325() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3326.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3326.java
 new file mode 100644
@@ -18623,12 +20772,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    private static final int VERSION = MCVersions.V23W06A;
 +
 +    private static void registerMob(final String id) {
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems"));
++        V100.registerEquipment(VERSION, id);
 +    }
 +
 +    public static void register() {
 +        registerMob("minecraft:sniffer");
 +    }
++
++    private V3326() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3327.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3327.java
 new file mode 100644
@@ -18652,6 +20803,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:decorated_pot", new DataWalkerItems("item"));
 +        MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:suspicious_sand", new DataWalkerItems("item"));
 +    }
++
++    private V3327() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3328.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3328.java
 new file mode 100644
@@ -18671,6 +20824,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    public static void register() {
 +        // registers simple entity "minecraft:interaction"
 +    }
++
++    private V3328() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3438.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3438.java
 new file mode 100644
@@ -18697,7 +20852,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +    public static void register() {
 +        // brushable block rename
-+        MCTypeRegistry.TILE_ENTITY.copyWalkers(VERSION,"minecraft:suspicious_sand", "minecraft:brushable_block");
++        MCTypeRegistry.TILE_ENTITY.copyWalkers(VERSION, "minecraft:suspicious_sand", "minecraft:brushable_block");
 +
 +        ConverterAbstractTileEntityRename.register(VERSION, new HashMap<>(Map.of(
 +                "minecraft:suspicious_sand", "minecraft:brushable_block"
@@ -18722,6 +20877,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                )
 +        )::get);
 +    }
++
++    private V3438() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3439.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3439.java
 new file mode 100644
@@ -18822,6 +20979,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:sign", signTileUpdater);
 +        MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:hanging_sign", signTileUpdater);
 +    }
++
++    private V3439() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3440.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3440.java
 new file mode 100644
@@ -18855,6 +21014,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                )
 +        )));
 +    }
++
++    private V3440() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3441.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3441.java
 new file mode 100644
@@ -18876,6 +21037,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        // See V3088 for why this converter is duplicated here and in V3088
 +        MCTypeRegistry.CHUNK.addStructureConverter(new ConverterAddBlendingData(VERSION));
 +    }
++
++    private V3441() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3447.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3447.java
 new file mode 100644
@@ -18929,6 +21092,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +        ConverterAbstractItemRename.register(VERSION, rename::get);
 +    }
++
++    private V3447() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3448.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3448.java
 new file mode 100644
@@ -18961,6 +21126,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            }
 +        });
 +    }
++
++    private V3448() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3450.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3450.java
 new file mode 100644
@@ -18988,6 +21155,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                )
 +        )::get));
 +    }
++
++    private V3450() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3451.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3451.java
 new file mode 100644
@@ -19030,6 +21199,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            }
 +        });
 +    }
++
++    private V3451() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3459.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3459.java
 new file mode 100644
@@ -19063,13 +21234,17 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +                final MapType<String> endData = dimensionData.getMap("1");
 +                if (endData != null) {
-+                    data.setMap("DragonFight", endData.getMap("DragonFight", endData.getTypeUtil().createEmptyMap()).copy());
++                    final MapType<String> dragonFight = endData.<String>getMap("DragonFight", endData.getTypeUtil().createEmptyMap()).copy();
++                    V3807.flattenBlockPos(dragonFight, "ExitPortalLocation");
++                    data.setMap("DragonFight", dragonFight);
 +                }
 +
 +                return null;
 +            }
 +        });
 +    }
++
++    private V3459() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3564.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3564.java
 new file mode 100644
@@ -19167,6 +21342,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:sign", converter);
 +        MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:hanging_sign", converter);
 +    }
++
++    private V3564() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3565.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3565.java
 new file mode 100644
@@ -19203,6 +21380,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            }
 +        });
 +    }
++
++    private V3565() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3566.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3566.java
 new file mode 100644
@@ -19265,6 +21444,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            }
 +        });
 +    }
++
++    private V3566() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3568.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3568.java
 new file mode 100644
@@ -19514,6 +21695,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +        MCTypeRegistry.ITEM_STACK.addStructureConverter(itemConverter);
 +    }
++
++    private V3568() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3682.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3682.java
 new file mode 100644
@@ -19534,6 +21717,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    public static void register() {
 +        MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:crafter", new DataWalkerItemLists("Items"));
 +    }
++
++    private V3682() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3683.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3683.java
 new file mode 100644
@@ -19571,6 +21756,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +        MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:tnt", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "block_state"));
 +    }
++
++    private V3683() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3685.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3685.java
 new file mode 100644
@@ -19639,6 +21826,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        registerArrowEntity("minecraft:spectral_arrow");
 +        registerArrowEntity("minecraft:arrow");
 +    }
++
++    private V3685() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3689.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3689.java
 new file mode 100644
@@ -19648,11 +21837,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 @@ -0,0 +0,0 @@
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
-+import ca.spottedleaf.dataconverter.converters.datatypes.DataWalker;
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
 +import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
 +import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils;
-+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists;
 +import ca.spottedleaf.dataconverter.types.ListType;
 +import ca.spottedleaf.dataconverter.types.MapType;
 +import ca.spottedleaf.dataconverter.types.ObjectType;
@@ -19662,12 +21849,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    private static final int VERSION = MCVersions.V23W44A + 1;
 +
 +    private static void registerMob(final String id) {
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems"));
++        V100.registerEquipment(VERSION, id);
 +    }
 +
 +    public static void register() {
 +        registerMob("minecraft:breeze");
 +        // minecraft:wind_charge is a simple entity
++        // minecraft:breeze_wind_charge is a simple entity
 +
 +        MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:trial_spawner", (final MapType<String> data, final long fromVersion, final long toVersion) -> {
 +            final ListType spawnPotentials = data.getList("spawn_potentials", ObjectType.MAP);
@@ -19677,11 +21865,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                }
 +            }
 +
-+            WalkerUtils.convert(MCTypeRegistry.ENTITY, data, "spawn_data", fromVersion, toVersion);
++            WalkerUtils.convert(MCTypeRegistry.ENTITY, data.getMap("spawn_data"), "entity", fromVersion, toVersion);
 +            return null;
 +        });
 +    }
 +
++    private V3689() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3692.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3692.java
 new file mode 100644
@@ -19708,9 +21897,1210 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    );
 +
 +    public static void register() {
-+        ConverterAbstractBlockRename.registerAndFixJigsaw(VERSION, GRASS_RENAME::get);
++        ConverterAbstractBlockRename.register(VERSION, GRASS_RENAME::get);
 +        ConverterAbstractItemRename.register(VERSION, GRASS_RENAME::get);
 +    }
++
++    private V3692() {}
++}
+diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3799.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3799.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3799.java
+@@ -0,0 +0,0 @@
++package ca.spottedleaf.dataconverter.minecraft.versions;
++
++import ca.spottedleaf.dataconverter.minecraft.MCVersions;
++
++public final class V3799 {
++
++    private static final int VERSION = MCVersions.V1_20_4 + 99;
++
++    public static void register() {
++        V100.registerEquipment(VERSION, "minecraft:armadillo");
++    }
++
++    private V3799() {}
++}
+diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3800.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3800.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3800.java
+@@ -0,0 +0,0 @@
++package ca.spottedleaf.dataconverter.minecraft.versions;
++
++import ca.spottedleaf.dataconverter.minecraft.MCVersions;
++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename;
++import java.util.HashMap;
++import java.util.Map;
++
++public final class V3800 {
++
++    private static final int VERSION = MCVersions.V1_20_4 + 100;
++
++    public static void register() {
++        final Map<String, String> renames = new HashMap<>(
++                Map.of(
++                        "minecraft:scute", "minecraft:turtle_scute"
++                )
++        );
++
++        ConverterAbstractItemRename.register(VERSION, renames::get);
++    }
++
++    private V3800() {}
++}
+diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3803.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3803.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3803.java
+@@ -0,0 +0,0 @@
++package ca.spottedleaf.dataconverter.minecraft.versions;
++
++import ca.spottedleaf.dataconverter.minecraft.MCVersions;
++import ca.spottedleaf.dataconverter.minecraft.converters.itemstack.ConverterEnchantmentsRename;
++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
++import java.util.HashMap;
++import java.util.Map;
++
++public final class V3803 {
++
++    private static final int VERSION = MCVersions.V23W51B + 1;
++
++    public static void register() {
++        final Map<String, String> renames = new HashMap<>(
++                Map.of(
++                        "minecraft:sweeping", "minecraft:sweeping_edge"
++                )
++        );
++
++        MCTypeRegistry.ITEM_STACK.addStructureConverter(new ConverterEnchantmentsRename(VERSION, renames::get));
++    }
++
++    private V3803() {}
++}
+diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3807.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3807.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3807.java
+@@ -0,0 +0,0 @@
++package ca.spottedleaf.dataconverter.minecraft.versions;
++
++import ca.spottedleaf.dataconverter.converters.DataConverter;
++import ca.spottedleaf.dataconverter.minecraft.MCVersions;
++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils;
++import ca.spottedleaf.dataconverter.types.ListType;
++import ca.spottedleaf.dataconverter.types.MapType;
++import ca.spottedleaf.dataconverter.types.ObjectType;
++
++public final class V3807 {
++
++    private static final int VERSION = MCVersions.V24W04A + 1;
++
++    public static void flattenBlockPos(final MapType<String> data, final String path) {
++        if (data == null) {
++            return;
++        }
++
++        final MapType<String> pos = data.getMap(path);
++        if (pos == null) {
++            return;
++        }
++
++        final Number x = pos.getNumber("X");
++        final Number y = pos.getNumber("Y");
++        final Number z = pos.getNumber("Z");
++
++        if (x == null || y == null || z == null) {
++            return;
++        }
++
++        data.setInts(path, new int[] { x.intValue(), y.intValue(), z.intValue() });
++    }
++
++    public static void register() {
++        // Step 0
++        MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:vault", (final MapType<String> root, final long fromVersion, final long toVersion) -> {
++            WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, root.getMap("config"), "key_item", fromVersion, toVersion);
++            WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, root.getMap("server_data"), "items_to_eject", fromVersion, toVersion);
++            WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, root.getMap("shared_data"), "display_item", fromVersion, toVersion);
++
++            return null;
++        });
++
++        // Step 1
++        MCTypeRegistry.SAVED_DATA_MAP_DATA.addStructureConverter(new DataConverter<>(VERSION, 1) {
++            @Override
++            public MapType<String> convert(final MapType<String> root, final long sourceVersion, final long toVersion) {
++                final MapType<String> data = root.getMap("data");
++
++                if (data == null) {
++                    return null;
++                }
++
++                final ListType banners = data.getList("banners", ObjectType.MAP);
++
++                if (banners == null) {
++                    return null;
++                }
++
++                for (int i = 0, len = banners.size(); i < len; ++i) {
++                    V3807.flattenBlockPos(banners.getMap(i), "Pos");
++                }
++
++                return null;
++            }
++        });
++    }
++
++    private V3807() {}
++}
+diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3808.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3808.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3808.java
+@@ -0,0 +0,0 @@
++package ca.spottedleaf.dataconverter.minecraft.versions;
++
++import ca.spottedleaf.dataconverter.converters.DataConverter;
++import ca.spottedleaf.dataconverter.minecraft.MCVersions;
++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists;
++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems;
++import ca.spottedleaf.dataconverter.types.ListType;
++import ca.spottedleaf.dataconverter.types.MapType;
++import ca.spottedleaf.dataconverter.types.ObjectType;
++
++public final class V3808 {
++
++    private static final int VERSION = MCVersions.V24W04A + 2;
++
++    public static void register() {
++        class BodyArmorConverter extends DataConverter<MapType<String>, MapType<String>> {
++            private final String path;
++            private final boolean clearArmor;
++
++            public BodyArmorConverter(final int toVersion, final String path, final boolean clearArmor) {
++                this(toVersion, 0, path, clearArmor);
++            }
++
++            public BodyArmorConverter(final int toVersion, final int versionStep, final String path, final boolean clearArmor) {
++                super(toVersion, versionStep);
++
++                this.path = path;
++                this.clearArmor = clearArmor;
++            }
++
++            @Override
++            public MapType<String> convert(final MapType<String> data, final long sourceVersion, final long toVersion) {
++                final MapType<String> prev = data.getMap(this.path);
++
++                if (prev == null) {
++                    return null;
++                }
++
++                data.remove(this.path);
++
++                data.setMap("body_armor_item", prev);
++                data.setFloat("body_armor_drop_chance", 2.0F);
++
++                if (this.clearArmor) {
++                    final ListType armor = data.getList("ArmorItems", ObjectType.MAP);
++                    if (armor.size() > 2) {
++                        armor.setMap(2, data.getTypeUtil().createEmptyMap());
++                    }
++
++                    final ListType chances = data.getList("ArmorDropChances", ObjectType.FLOAT);
++                    if (chances.size() > 2) {
++                        chances.setFloat(2, 0.085F);
++                    }
++                }
++
++                return null;
++            }
++        }
++
++        // Step 0
++        MCTypeRegistry.ENTITY.addConverterForId("minecraft:horse", new BodyArmorConverter(VERSION, "ArmorItem", true));
++
++        MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:horse", new DataWalkerItems("SaddleItem"));
++        V100.registerEquipment(VERSION, "minecraft:horse");
++
++        // Step 1
++        MCTypeRegistry.ENTITY.addConverterForId("minecraft:llama", new BodyArmorConverter(VERSION, 1, "DecorItem", false));
++
++        MCTypeRegistry.ENTITY.addWalker(VERSION, 1, "minecraft:llama", new DataWalkerItemLists("Items"));
++        MCTypeRegistry.ENTITY.addWalker(VERSION, 1, "minecraft:llama", new DataWalkerItems("SaddleItem"));
++        V100.registerEquipment(VERSION, 1, "minecraft:llama");
++    }
++
++    private V3808() {}
++}
+diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3809.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3809.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3809.java
+@@ -0,0 +0,0 @@
++package ca.spottedleaf.dataconverter.minecraft.versions;
++
++import ca.spottedleaf.dataconverter.converters.DataConverter;
++import ca.spottedleaf.dataconverter.minecraft.MCVersions;
++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
++import ca.spottedleaf.dataconverter.types.ListType;
++import ca.spottedleaf.dataconverter.types.MapType;
++import ca.spottedleaf.dataconverter.types.ObjectType;
++
++public final class V3809 {
++
++    private static final int VERSION = MCVersions.V24W05A;
++
++    public static void register() {
++        final DataConverter<MapType<String>, MapType<String>> slotConverter = new DataConverter<>(VERSION) {
++            @Override
++            public MapType<String> convert(final MapType<String> data, final long sourceVersion, final long toVersion) {
++                final ListType items = data.getList("Items", ObjectType.MAP);
++                if (items == null) {
++                    return null;
++                }
++
++                for (int i = 0, len = items.size(); i < len; ++i) {
++                    final MapType<String> item = items.getMap(i);
++
++                    final int slot = item.getInt("Slot", 2);
++                    item.setByte("Slot", (byte)(slot - 2));
++                }
++
++                return null;
++            }
++        };
++
++        MCTypeRegistry.ENTITY.addConverterForId("minecraft:llama", slotConverter);
++        MCTypeRegistry.ENTITY.addConverterForId("minecraft:mule", slotConverter);
++        MCTypeRegistry.ENTITY.addConverterForId("minecraft:donkey", slotConverter);
++    }
++
++    private V3809() {}
++}
+diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3812.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3812.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3812.java
+@@ -0,0 +0,0 @@
++package ca.spottedleaf.dataconverter.minecraft.versions;
++
++import ca.spottedleaf.dataconverter.converters.DataConverter;
++import ca.spottedleaf.dataconverter.minecraft.MCVersions;
++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
++import ca.spottedleaf.dataconverter.types.ListType;
++import ca.spottedleaf.dataconverter.types.MapType;
++import ca.spottedleaf.dataconverter.types.ObjectType;
++import ca.spottedleaf.dataconverter.util.NamespaceUtil;
++
++public final class V3812 {
++
++    private static final int VERSION = MCVersions.V24W05B + 1;
++
++    public static void register() {
++        MCTypeRegistry.ENTITY.addConverterForId("minecraft:wolf", new DataConverter<>(VERSION) {
++            @Override
++            public MapType<String> convert(final MapType<String> data, final long sourceVersion, final long toVersion) {
++                boolean doubleHealth = false;
++
++                final ListType attributes = data.getList("Attributes", ObjectType.MAP);
++                if (attributes != null) {
++                    for (int i = 0, len = attributes.size(); i < len; ++i) {
++                        final MapType<String> attribute = attributes.getMap(i);
++
++                        if (!"minecraft:generic.max_health".equals(NamespaceUtil.correctNamespace(attribute.getString("Name")))) {
++                            continue;
++                        }
++
++                        final double base = attribute.getDouble("Base", 0.0D);
++                        if (base == 20.0D) {
++                            attribute.setDouble("Base", 40.0D);
++                            doubleHealth = true;
++                        }
++                    }
++                }
++
++                if (doubleHealth) {
++                    data.setFloat("Health", data.getFloat("Health", 0.0F) * 2.0F);
++                }
++
++                return null;
++            }
++        });
++    }
++
++    private V3812() {}
++}
+diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3813.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3813.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3813.java
+@@ -0,0 +0,0 @@
++package ca.spottedleaf.dataconverter.minecraft.versions;
++
++import ca.spottedleaf.dataconverter.converters.DataConverter;
++import ca.spottedleaf.dataconverter.minecraft.MCVersions;
++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper;
++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
++import ca.spottedleaf.dataconverter.types.ListType;
++import ca.spottedleaf.dataconverter.types.MapType;
++import ca.spottedleaf.dataconverter.types.ObjectType;
++
++public final class V3813 {
++
++    private static final int VERSION = MCVersions.V24W05B + 2;
++
++    private static final String[] PATROLLING_MOBS = new String[] {
++            "minecraft:witch",
++            "minecraft:ravager",
++            "minecraft:pillager",
++            "minecraft:illusioner",
++            "minecraft:evoker",
++            "minecraft:vindicator"
++    };
++
++    public static void register() {
++        class RootPositionConverter extends DataConverter<MapType<String>, MapType<String>> {
++            private final RenamePair[] convert;
++
++            public RootPositionConverter(final int toVersion, final RenamePair[] convert) {
++                this(toVersion, 0, convert);
++            }
++
++            public RootPositionConverter(final int toVersion, final int versionStep, final RenamePair[] convert) {
++                super(toVersion, versionStep);
++                this.convert = convert;
++            }
++
++            @Override
++            public MapType<String> convert(final MapType<String> data, final long sourceVersion, final long toVersion) {
++                for (final RenamePair rename : this.convert) {
++                    V3807.flattenBlockPos(data, rename.from);
++                    RenameHelper.renameSingle(data, rename.from, rename.to);
++                }
++                return null;
++            }
++        }
++
++        MCTypeRegistry.ENTITY.addConverterForId("minecraft:bee", new RootPositionConverter(VERSION, new RenamePair[] {
++                new RenamePair("HivePos", "hive_pos"),
++                new RenamePair("FlowerPos", "flower_pos")
++        }));
++        MCTypeRegistry.ENTITY.addConverterForId("minecraft:end_crystal", new RootPositionConverter(VERSION, new RenamePair[] {
++                new RenamePair("BeamTarget", "beam_target"),
++        }));
++        MCTypeRegistry.ENTITY.addConverterForId("minecraft:wandering_trader", new RootPositionConverter(VERSION, new RenamePair[] {
++                new RenamePair("WanderTarget", "wander_target"),
++        }));
++
++        final RootPositionConverter patrolConverter = new RootPositionConverter(VERSION, new RenamePair[] {
++                new RenamePair("PatrolTarget", "patrol_target"),
++        });
++        for (final String id : PATROLLING_MOBS) {
++            MCTypeRegistry.ENTITY.addConverterForId(id, patrolConverter);
++        }
++
++        MCTypeRegistry.ENTITY.addStructureConverter(new RootPositionConverter(VERSION, new RenamePair[] {
++                new RenamePair("Leash", "leash"),
++        }));
++
++
++        MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:beehive", new RootPositionConverter(VERSION, new RenamePair[] {
++                new RenamePair("FlowerPos", "flower_pos"),
++        }));
++        MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:end_gateway", new RootPositionConverter(VERSION, new RenamePair[] {
++                new RenamePair("ExitPortal", "exit_portal"),
++        }));
++
++        MCTypeRegistry.SAVED_DATA_MAP_DATA.addStructureConverter(new DataConverter<>(VERSION) {
++            @Override
++            public MapType<String> convert(final MapType<String> root, final long sourceVersion, final long toVersion) {
++                final MapType<String> data = root.getMap("data");
++
++                if (data == null) {
++                    return null;
++                }
++
++                final ListType frames = data.getList("frames", ObjectType.MAP);
++                if (frames != null) {
++                    for (int i = 0, len = frames.size(); i < len; ++i) {
++                        final MapType<String> frame = frames.getMap(i);
++
++                        V3807.flattenBlockPos(frame, "Pos");
++
++                        RenameHelper.renameSingle(frame, "Pos", "pos");
++                        RenameHelper.renameSingle(frame, "Rotation", "rotation");
++                        RenameHelper.renameSingle(frame, "EntityId", "entity_id");
++                    }
++                }
++
++                final ListType banners = data.getList("banners", ObjectType.MAP);
++                for (int i = 0, len = banners.size(); i < len; ++i) {
++                    final MapType<String> banner = banners.getMap(i);
++
++                    RenameHelper.renameSingle(banner, "Pos", "pos");
++                    RenameHelper.renameSingle(banner, "Color", "color");
++                    RenameHelper.renameSingle(banner, "Name", "name");
++                }
++
++                return null;
++            }
++        });
++
++        MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:compass", new DataConverter<>(VERSION) {
++            @Override
++            public MapType<String> convert(final MapType<String> data, final long sourceVersion, final long toVersion) {
++                final MapType<String> tag = data.getMap("tag");
++
++                if (tag == null) {
++                    return null;
++                }
++
++                V3807.flattenBlockPos(tag, "LodestonePos");
++
++                return null;
++            }
++        });
++    }
++
++    private V3813() {}
++    
++    private static record RenamePair(String from, String to) {}
++}
+diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3814.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3814.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3814.java
+@@ -0,0 +0,0 @@
++package ca.spottedleaf.dataconverter.minecraft.versions;
++
++import ca.spottedleaf.dataconverter.minecraft.MCVersions;
++import ca.spottedleaf.dataconverter.minecraft.converters.attributes.ConverterAbstractAttributesRename;
++import java.util.HashMap;
++import java.util.Map;
++
++public final class V3814 {
++
++    private static final int VERSION = MCVersions.V24W05B + 3;
++
++    public static void register() {
++        final Map<String, String> renames = new HashMap<>(
++                Map.of("minecraft:horse.jump_strength", "minecraft:generic.jump_strength")
++        );
++
++        ConverterAbstractAttributesRename.register(VERSION, renames::get);
++    }
++
++    private V3814() {}
++}
+diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3816.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3816.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3816.java
+@@ -0,0 +0,0 @@
++package ca.spottedleaf.dataconverter.minecraft.versions;
++
++import ca.spottedleaf.dataconverter.minecraft.MCVersions;
++
++public final class V3816 {
++
++    private static final int VERSION = MCVersions.V24W06A + 1;
++
++    public static void register() {
++        V100.registerEquipment(VERSION, "minecraft:bogged");
++    }
++
++    private V3816() {}
++}
+diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3818.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3818.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3818.java
+@@ -0,0 +0,0 @@
++package ca.spottedleaf.dataconverter.minecraft.versions;
++
++import ca.spottedleaf.dataconverter.converters.DataConverter;
++import ca.spottedleaf.dataconverter.converters.datatypes.DataWalker;
++import ca.spottedleaf.dataconverter.minecraft.MCVersions;
++import ca.spottedleaf.dataconverter.minecraft.converters.custom.V3818_Commands;
++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper;
++import ca.spottedleaf.dataconverter.minecraft.converters.itemstack.ConverterItemStackToDataComponents;
++import ca.spottedleaf.dataconverter.minecraft.converters.particle.ConverterParticleToNBT;
++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils;
++import ca.spottedleaf.dataconverter.types.ListType;
++import ca.spottedleaf.dataconverter.types.MapType;
++import ca.spottedleaf.dataconverter.types.ObjectType;
++import ca.spottedleaf.dataconverter.types.Types;
++import java.util.HashMap;
++import java.util.Map;
++
++public final class V3818 {
++
++    private static final int VERSION = MCVersions.V24W07A + 1;
++
++    private static final String[] BANNER_COLOURS = new String[] {
++            "white",
++            "orange",
++            "magenta",
++            "light_blue",
++            "yellow",
++            "lime",
++            "pink",
++            "gray",
++            "light_gray",
++            "cyan",
++            "purple",
++            "blue",
++            "brown",
++            "green",
++            "red",
++            "black",
++    };
++
++    public static String getBannerColour(final int id) {
++        return id >= 0 && id < BANNER_COLOURS.length ? BANNER_COLOURS[id] : BANNER_COLOURS[0];
++    }
++
++    public static void register() {
++        // Step 0
++        // Note: No breakpoint needed, nothing nests hotbar
++        MCTypeRegistry.HOTBAR.addStructureConverter(new DataConverter<>(VERSION) {
++            @Override
++            public MapType<String> convert(final MapType<String> data, final long sourceVersion, final long toVersion) {
++                for (final String key : data.keys()) {
++                    final ListType itemList = data.getList(key, ObjectType.MAP);
++                    if (itemList != null) {
++                        for (int i = 0, len = itemList.size(); i < len; ++i) {
++                            final MapType<String> item = itemList.getMap(i);
++
++                            final String id = item.getString("id");
++                            final int count = item.getInt("Count");
++
++                            if ("minecraft:air".equals(id) || count <= 0) {
++                                itemList.setMap(i, item.getTypeUtil().createEmptyMap());
++                            }
++                        }
++                    }
++                }
++
++                return null;
++            }
++        });
++
++        MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:beehive", new DataConverter<>(VERSION) {
++            @Override
++            public MapType<String> convert(final MapType<String> data, final long sourceVersion, final long toVersion) {
++                RenameHelper.renameSingle(data, "Bees", "bees");
++
++                final ListType bees = data.getList("bees", ObjectType.MAP);
++                if (bees != null) {
++                    for (int i = 0, len = bees.size(); i < len; ++i) {
++                        final MapType<String> bee = bees.getMap(i);
++
++                        RenameHelper.renameSingle(bee, "EntityData", "entity_data");
++                        RenameHelper.renameSingle(bee, "TicksInHive", "ticks_in_hive");
++                        RenameHelper.renameSingle(bee, "MinOccupationTicks", "min_ticks_in_hive");
++                    }
++                }
++
++                return null;
++            }
++        });
++        MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:beehive", (final MapType<String> root, final long fromVersion, final long toVersion) -> {
++            WalkerUtils.convertListPath(MCTypeRegistry.ENTITY, root, "bees", "entity_data", fromVersion, toVersion);
++
++            return null;
++        });
++
++        // Step 1
++        MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:banner", new DataConverter<>(VERSION, 1) {
++            private static final Map<String, String> PATTERN_UPDATE = new HashMap<>();
++            static {
++                PATTERN_UPDATE.put("b", "minecraft:base");
++                PATTERN_UPDATE.put("bl", "minecraft:square_bottom_left");
++                PATTERN_UPDATE.put("br", "minecraft:square_bottom_right");
++                PATTERN_UPDATE.put("tl", "minecraft:square_top_left");
++                PATTERN_UPDATE.put("tr", "minecraft:square_top_right");
++                PATTERN_UPDATE.put("bs", "minecraft:stripe_bottom");
++                PATTERN_UPDATE.put("ts", "minecraft:stripe_top");
++                PATTERN_UPDATE.put("ls", "minecraft:stripe_left");
++                PATTERN_UPDATE.put("rs", "minecraft:stripe_right");
++                PATTERN_UPDATE.put("cs", "minecraft:stripe_center");
++                PATTERN_UPDATE.put("ms", "minecraft:stripe_middle");
++                PATTERN_UPDATE.put("drs", "minecraft:stripe_downright");
++                PATTERN_UPDATE.put("dls", "minecraft:stripe_downleft");
++                PATTERN_UPDATE.put("ss", "minecraft:small_stripes");
++                PATTERN_UPDATE.put("cr", "minecraft:cross");
++                PATTERN_UPDATE.put("sc", "minecraft:straight_cross");
++                PATTERN_UPDATE.put("bt", "minecraft:triangle_bottom");
++                PATTERN_UPDATE.put("tt", "minecraft:triangle_top");
++                PATTERN_UPDATE.put("bts", "minecraft:triangles_bottom");
++                PATTERN_UPDATE.put("tts", "minecraft:triangles_top");
++                PATTERN_UPDATE.put("ld", "minecraft:diagonal_left");
++                PATTERN_UPDATE.put("rd", "minecraft:diagonal_up_right");
++                PATTERN_UPDATE.put("lud", "minecraft:diagonal_up_left");
++                PATTERN_UPDATE.put("rud", "minecraft:diagonal_right");
++                PATTERN_UPDATE.put("mc", "minecraft:circle");
++                PATTERN_UPDATE.put("mr", "minecraft:rhombus");
++                PATTERN_UPDATE.put("vh", "minecraft:half_vertical");
++                PATTERN_UPDATE.put("hh", "minecraft:half_horizontal");
++                PATTERN_UPDATE.put("vhr", "minecraft:half_vertical_right");
++                PATTERN_UPDATE.put("hhb", "minecraft:half_horizontal_bottom");
++                PATTERN_UPDATE.put("bo", "minecraft:border");
++                PATTERN_UPDATE.put("cbo", "minecraft:curly_border");
++                PATTERN_UPDATE.put("gra", "minecraft:gradient");
++                PATTERN_UPDATE.put("gru", "minecraft:gradient_up");
++                PATTERN_UPDATE.put("bri", "minecraft:bricks");
++                PATTERN_UPDATE.put("glb", "minecraft:globe");
++                PATTERN_UPDATE.put("cre", "minecraft:creeper");
++                PATTERN_UPDATE.put("sku", "minecraft:skull");
++                PATTERN_UPDATE.put("flo", "minecraft:flower");
++                PATTERN_UPDATE.put("moj", "minecraft:mojang");
++                PATTERN_UPDATE.put("pig", "minecraft:piglin");
++            }
++
++            @Override
++            public MapType<String> convert(final MapType<String> data, final long sourceVersion, final long toVersion) {
++                final ListType patterns = data.getList("Patterns", ObjectType.MAP);
++                if (patterns != null) {
++                    for (int i = 0, len = patterns.size(); i < len; ++i) {
++                        final MapType<String> pattern = patterns.getMap(i);
++
++                        final String patternName = pattern.getString("Pattern");
++                        if (patternName != null) {
++                            final String renamed = PATTERN_UPDATE.get(patternName);
++                            if (renamed != null) {
++                                pattern.setString("Pattern", renamed);
++                            }
++                        }
++                        RenameHelper.renameSingle(pattern, "Pattern", "pattern");
++
++                        final String newColour = getBannerColour(pattern.getInt("Color"));
++                        pattern.setString("Color", newColour);
++                        RenameHelper.renameSingle(pattern, "Color", "color");
++                    }
++                }
++                RenameHelper.renameSingle(data, "Patterns", "patterns");
++
++                return null;
++            }
++        });
++
++        // Step 2
++        // Note: there is nothing after the previous breakpoint (1.19.4) that reads nested entity item
++        MCTypeRegistry.ENTITY.addConverterForId("minecraft:arrow", new DataConverter<>(VERSION, 2) {
++            @Override
++            public MapType<String> convert(final MapType<String> data, final long sourceVersion, final long toVersion) {
++                final Object potion = data.getGeneric("Potion");
++                final Object customPotionEffects = data.getGeneric("custom_potion_effects");
++                final Object color = data.getGeneric("Color");
++
++                if (potion == null && customPotionEffects == null && color == null) {
++                    return null;
++                }
++
++                data.remove("Potion");
++                data.remove("custom_potion_effects");
++                data.remove("Color");
++
++                final MapType<String> item = data.getMap("item");
++                if (item == null) {
++                    return null;
++                }
++
++                final MapType<String> tag = item.getOrCreateMap("tag");
++
++                if (potion != null) {
++                    tag.setGeneric("Potion", potion);
++                }
++                if (customPotionEffects != null) {
++                    tag.setGeneric("custom_potion_effects", customPotionEffects);
++                }
++                if (color != null) {
++                    tag.setGeneric("CustomPotionColor", color);
++                }
++
++                return null;
++            }
++        });
++
++        // Step 3
++        MCTypeRegistry.DATA_COMPONENTS.addStructureWalker(VERSION, new DataWalker<>() {
++            private static void walkBlockPredicates(final MapType<String> root, final long fromVersion, final long toVersion) {
++                if (root.hasKey("blocks", ObjectType.STRING)) {
++                    WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, root, "blocks", fromVersion, toVersion);
++                } else if (root.hasKey("blocks", ObjectType.LIST)) {
++                    WalkerUtils.convertList(MCTypeRegistry.BLOCK_NAME, root, "blocks", fromVersion, toVersion);
++                }
++            }
++
++            @Override
++            public MapType<String> walk(final MapType<String> root, final long fromVersion, final long toVersion) {
++                WalkerUtils.convertListPath(MCTypeRegistry.ENTITY, root, "minecraft:bees", "entity_data", fromVersion, toVersion);
++
++                WalkerUtils.convert(MCTypeRegistry.TILE_ENTITY, root, "minecraft:block_entity_data", fromVersion, toVersion);
++                WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, root, "minecraft:bundle_contents", fromVersion, toVersion);
++
++                final MapType<String> canBreak = root.getMap("minecraft:can_break");
++                if (canBreak != null) {
++                    final ListType predicates = canBreak.getList("predicates", ObjectType.MAP);
++                    if (predicates != null) {
++                        for (int i = 0, len = predicates.size(); i < len; ++i) {
++                            walkBlockPredicates(predicates.getMap(i), fromVersion, toVersion);
++                        }
++                    }
++                    // Not handled by DFU: simple encoding does not require "predicates"
++                    walkBlockPredicates(canBreak, fromVersion, toVersion);
++                }
++
++                final MapType<String> canPlaceOn = root.getMap("minecraft:can_break");
++                if (canPlaceOn != null) {
++                    final ListType predicates = canPlaceOn.getList("predicates", ObjectType.MAP);
++                    if (predicates != null) {
++                        for (int i = 0, len = predicates.size(); i < len; ++i) {
++                            walkBlockPredicates(predicates.getMap(i), fromVersion, toVersion);
++                        }
++                    }
++                    // Not handled by DFU: simple encoding does not require "predicates"
++                    walkBlockPredicates(canPlaceOn, fromVersion, toVersion);
++                }
++
++                WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, root, "minecraft:charged_projectiles", fromVersion, toVersion);
++                WalkerUtils.convertListPath(MCTypeRegistry.ITEM_STACK, root, "minecraft:container", "item", fromVersion, toVersion);
++                WalkerUtils.convert(MCTypeRegistry.ENTITY, root, "minecraft:entity_data", fromVersion, toVersion);
++                WalkerUtils.convertList(MCTypeRegistry.ITEM_NAME, root, "minecraft:pot_decorations", fromVersion, toVersion);
++
++                return null;
++            }
++        });
++
++        // Step 4
++        MCTypeRegistry.PARTICLE.addStructureConverter(new DataConverter<>(VERSION, 4) {
++            @Override
++            public MapType<String> convert(final Object input, final long sourceVersion, final long toVersion) {
++                if (!(input instanceof String flat)) {
++                    return null;
++                }
++
++                return ConverterParticleToNBT.convert(flat, Types.NBT);
++            }
++        });
++
++        MCTypeRegistry.PARTICLE.addStructureWalker(VERSION, 4, (final Object input, final long fromVersion, final long toVersion) -> {
++            if (!(input instanceof MapType<?>)) {
++                return null;
++            }
++
++            final MapType<String> root = (MapType<String>)input;
++
++            WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, root, "item", fromVersion, toVersion);
++            WalkerUtils.convert(MCTypeRegistry.BLOCK_STATE, root, "block_state", fromVersion, toVersion);
++
++            return null;
++        });
++
++        // Step 5
++        // Note: needs breakpoint, reads nested tile entity data
++        MCTypeRegistry.ITEM_STACK.addStructureConverter(new DataConverter<>(VERSION, 5) {
++            @Override
++            public MapType<String> convert(final MapType<String> data, final long sourceVersion, final long toVersion) {
++                return ConverterItemStackToDataComponents.convertItem(data);
++            }
++        });
++
++        MCTypeRegistry.ITEM_STACK.addStructureWalker(VERSION, 5, (final MapType<String> root, final long fromVersion, final long toVersion) -> {
++            WalkerUtils.convert(MCTypeRegistry.ITEM_NAME, root, "id", fromVersion, toVersion);
++            WalkerUtils.convert(MCTypeRegistry.DATA_COMPONENTS, root, "components", fromVersion, toVersion);
++
++            return null;
++        });
++
++        // Custom converter for converting commands inside signs, books, command blocks
++        V3818_Commands.register_5();
++
++        // Step 6
++        MCTypeRegistry.ENTITY.addConverterForId("minecraft:area_effect_cloud", new DataConverter<>(VERSION, 6) {
++            @Override
++            public MapType<String> convert(final MapType<String> data, final long sourceVersion, final long toVersion) {
++                final Object color = data.getGeneric("Color");
++                final Object effects = data.getGeneric("effects");
++                final Object potion = data.getGeneric("Potion");
++
++                if (color == null && effects == null && potion == null) {
++                    return null;
++                }
++                data.remove("Color");
++                data.remove("effects");
++                data.remove("Potion");
++
++                final MapType<String> potionContents = data.getTypeUtil().createEmptyMap();
++                data.setMap("potion_contents", potionContents);
++
++                if (color != null) {
++                    potionContents.setGeneric("custom_color", color);
++                }
++
++                if (effects != null) {
++                    potionContents.setGeneric("custom_effects", effects);
++                }
++
++                if (potion != null) {
++                    potionContents.setGeneric("potion", potion);
++                }
++
++                return null;
++            }
++        });
++    }
++
++    private V3818() {}
++}
+diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3820.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3820.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3820.java
+@@ -0,0 +0,0 @@
++package ca.spottedleaf.dataconverter.minecraft.versions;
++
++import ca.spottedleaf.dataconverter.converters.DataConverter;
++import ca.spottedleaf.dataconverter.minecraft.MCVersions;
++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper;
++import ca.spottedleaf.dataconverter.minecraft.converters.itemstack.ConverterItemStackToDataComponents;
++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
++import ca.spottedleaf.dataconverter.types.MapType;
++
++public final class V3820 {
++
++    private static final int VERSION = MCVersions.V24W09A + 1;
++
++    public static void register() {
++        MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:skull", new DataConverter<>(VERSION) {
++            @Override
++            public MapType<String> convert(final MapType<String> data, final long sourceVersion, final long toVersion) {
++                final Object skullOwner = data.getGeneric("SkullOwner");
++                final Object extraType = data.getGeneric("ExtraType");
++
++                if (skullOwner == null && extraType == null) {
++                    return null;
++                }
++
++                data.remove("SkullOwner");
++                data.remove("ExtraType");
++
++                data.setMap(
++                        "profile",
++                        ConverterItemStackToDataComponents.convertProfile(
++                                skullOwner == null ? extraType : skullOwner, data.getTypeUtil()
++                        )
++                );
++
++                return null;
++            }
++        });
++        // I don't see why this converter is necessary, V3818 should have converted correctly...
++        MCTypeRegistry.ITEM_STACK.addStructureConverter(new DataConverter<>(VERSION) {
++            @Override
++            public MapType<String> convert(final MapType<String> data, final long sourceVersion, final long toVersion) {
++                final MapType<String> components = data.getMap("components");
++
++                if (components == null) {
++                    return null;
++                }
++
++                final MapType<String> oldTarget = components.getMap("minecraft:lodestone_target");
++                if (oldTarget == null) {
++                    return null;
++                }
++
++                components.remove("minecraft:lodestone_target");
++                components.setMap("minecraft:lodestone_tracker", oldTarget);
++
++                final Object pos = oldTarget.getMap("pos");
++                final Object dim = oldTarget.getMap("dimension");
++
++                if (pos == null && dim == null) {
++                    return null;
++                }
++
++                oldTarget.remove("pos");
++                oldTarget.remove("dimension");
++
++                final MapType<String> target = oldTarget.getTypeUtil().createEmptyMap();
++                oldTarget.setMap("target", target);
++
++                target.setGeneric("pos", pos);
++                target.setGeneric("dimension", dim);
++
++                return null;
++            }
++        });
++    }
++
++    private V3820() {}
++}
+diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3825.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3825.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3825.java
+@@ -0,0 +0,0 @@
++package ca.spottedleaf.dataconverter.minecraft.versions;
++
++import ca.spottedleaf.dataconverter.converters.DataConverter;
++import ca.spottedleaf.dataconverter.minecraft.MCVersions;
++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
++import ca.spottedleaf.dataconverter.minecraft.util.ComponentUtils;
++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils;
++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems;
++import ca.spottedleaf.dataconverter.types.ListType;
++import ca.spottedleaf.dataconverter.types.MapType;
++import ca.spottedleaf.dataconverter.types.ObjectType;
++import java.util.Arrays;
++import java.util.HashSet;
++import java.util.Set;
++
++public final class V3825 {
++
++    private static final int VERSION = MCVersions.V24W12A + 1;
++
++    private static final Set<String> BANNER_NAMES = new HashSet<>(
++            Arrays.asList(
++                    "block.minecraft.ominous_banner"
++            )
++    );
++    private static final Set<String> MAP_NAMES = new HashSet<>(
++            Arrays.asList(
++                    "filled_map.buried_treasure",
++                    "filled_map.explorer_jungle",
++                    "filled_map.explorer_swamp",
++                    "filled_map.mansion",
++                    "filled_map.monument",
++                    "filled_map.trial_chambers",
++                    "filled_map.village_desert",
++                    "filled_map.village_plains",
++                    "filled_map.village_savanna",
++                    "filled_map.village_snowy",
++                    "filled_map.village_taiga"
++            )
++    );
++
++    public static void register() {
++        MCTypeRegistry.ITEM_STACK.addStructureConverter(new DataConverter<>(VERSION) {
++            private static void convertName(final MapType<String> components, final Set<String> standardNames) {
++                final String customName = components.getString("minecraft:custom_name");
++                if (customName == null) {
++                    return;
++                }
++
++                final String translation = ComponentUtils.retrieveTranslationString(customName);
++                if (translation == null) {
++                    return;
++                }
++
++                if (standardNames.contains(translation)) {
++                    components.remove("minecraft:custom_name");
++                    components.setString("minecraft:item_name", customName);
++                }
++            }
++
++            @Override
++            public MapType<String> convert(final MapType<String> data, final long sourceVersion, final long toVersion) {
++                final MapType<String> components = data.getMap("components");
++                if (components == null) {
++                    return null;
++                }
++
++                final String id = data.getString("id");
++                if (id == null) {
++                    return null;
++                }
++
++                switch (id) {
++                    case "minecraft:white_banner": {
++                        convertName(components, BANNER_NAMES);
++                        break;
++                    }
++                    case "minecraft:filled_map": {
++                        convertName(components, MAP_NAMES);
++                        break;
++                    }
++                }
++
++                return null;
++            }
++        });
++        MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:banner", new DataConverter<>(VERSION) {
++            @Override
++            public MapType<String> convert(final MapType<String> data, final long sourceVersion, final long toVersion) {
++                final String customName = data.getString("CustomName");
++                if (customName == null || !"block.minecraft.ominous_banner".equals(ComponentUtils.retrieveTranslationString(customName))) {
++                    return null;
++                }
++
++                data.remove("CustomName");
++
++                final MapType<String> components = data.getOrCreateMap("components");
++
++                components.setString("minecraft:item_name", customName);
++                components.setMap("minecraft:hide_additional_tooltip", components.getTypeUtil().createEmptyMap());
++
++                return null;
++            }
++        });
++        // DFU does not change the schema, even though it moves spawn_potentials
++        MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:trial_spawner", (final MapType<String> data, final long fromVersion, final long toVersion) -> {
++            final MapType<String> normalConfig = data.getMap("normal_config");
++            if (normalConfig != null) {
++                WalkerUtils.convertListPath(MCTypeRegistry.ENTITY, normalConfig, "spawn_potentials", "data", "entity", fromVersion, toVersion);
++            }
++            final MapType<String> ominousConfig = data.getMap("ominous_config");
++            if (ominousConfig != null) {
++                WalkerUtils.convertListPath(MCTypeRegistry.ENTITY, normalConfig, "spawn_potentials", "data", "entity", fromVersion, toVersion);
++            }
++
++            WalkerUtils.convert(MCTypeRegistry.ENTITY, data.getMap("spawn_data"), "entity", fromVersion, toVersion);
++            return null;
++        });
++        MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:trial_spawner", new DataConverter<>(VERSION) {
++            private static final String[] NORMAL_CONFIG_KEYS = new String[] {
++                    "spawn_range",
++                    "total_mobs",
++                    "simultaneous_mobs",
++                    "total_mobs_added_per_player",
++                    "simultaneous_mobs_added_per_player",
++                    "ticks_between_spawn",
++                    "spawn_potentials",
++                    "loot_tables_to_eject",
++                    "items_to_drop_when_ominous"
++            };
++
++            @Override
++            public MapType<String> convert(final MapType<String> data, final long sourceVersion, final long toVersion) {
++                final MapType<String> normalConfig = data.getTypeUtil().createEmptyMap();
++
++                for (final String normalKey : NORMAL_CONFIG_KEYS) {
++                    final Object normalValue = data.getGeneric(normalKey);
++                    if (normalValue != null) {
++                        data.remove(normalKey);
++                        normalConfig.setGeneric(normalKey, normalValue);
++                    }
++                }
++
++                if (!normalConfig.isEmpty()) {
++                    data.setMap("normal_config", normalConfig);
++                }
++
++                return null;
++            }
++        });
++
++        MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:ominous_item_spawner", new DataWalkerItems("item"));
++    }
++
++    private V3825() {}
++}
+diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3828.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3828.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3828.java
+@@ -0,0 +0,0 @@
++package ca.spottedleaf.dataconverter.minecraft.versions;
++
++import ca.spottedleaf.dataconverter.converters.DataConverter;
++import ca.spottedleaf.dataconverter.minecraft.MCVersions;
++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
++import ca.spottedleaf.dataconverter.types.MapType;
++import ca.spottedleaf.dataconverter.util.NamespaceUtil;
++
++public final class V3828 {
++
++    private static final int VERSION = MCVersions.V24W14A + 1;
++
++    public static void register() {
++        MCTypeRegistry.VILLAGER_TRADE.addStructureConverter(new DataConverter<>(VERSION) {
++            @Override
++            public MapType<String> convert(final MapType<String> data, final long sourceVersion, final long toVersion) {
++                final MapType<String> buyB = data.getMap("buyB");
++
++                if (buyB == null) {
++                    return null;
++                }
++
++                final String id = NamespaceUtil.correctNamespace(buyB.getString("id", "minecraft:air"));
++                final int count = buyB.getInt("count", 0);
++
++                // Fix DFU: use count <= 0 instead of count == 0
++                if ("minecraft:air".equals(id) || count <= 0) {
++                    data.remove("buyB");
++                }
++
++                return null;
++            }
++        });
++    }
++
++    private V3828() {}
++}
+diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3833.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3833.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3833.java
+@@ -0,0 +0,0 @@
++package ca.spottedleaf.dataconverter.minecraft.versions;
++
++import ca.spottedleaf.dataconverter.converters.DataConverter;
++import ca.spottedleaf.dataconverter.minecraft.MCVersions;
++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
++import ca.spottedleaf.dataconverter.types.MapType;
++import ca.spottedleaf.dataconverter.util.NamespaceUtil;
++
++public final class V3833 {
++
++    private static final int VERSION = MCVersions.V1_20_5_PRE4 + 1;
++
++    public static void register() {
++        MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:brushable_block", new DataConverter<>(VERSION) {
++            @Override
++            public MapType<String> convert(final MapType<String> data, final long sourceVersion, final long toVersion) {
++                final MapType<String> item = data.getMap("item");
++                if (item == null) {
++                    return null;
++                }
++
++
++                final String id = NamespaceUtil.correctNamespace(item.getString("id", "minecraft:air"));
++                final int count = item.getInt("count", 0);
++
++                // Fix DFU: use count <= 0 instead of count == 0
++                if ("minecraft:air".equals(id) || count <= 0) {
++                    data.remove("item");
++                }
++
++                return null;
++            }
++        });
++    }
++
++    private V3833() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V501.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V501.java
 new file mode 100644
@@ -19721,15 +23111,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +package ca.spottedleaf.dataconverter.minecraft.versions;
 +
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
-+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
-+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists;
 +
 +public final class V501 {
 +
-+    protected static final int VERSION = MCVersions.V16W20A;
++    private static final int VERSION = MCVersions.V16W20A;
 +
 +    private static void registerMob(final String id) {
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems"));
++        V100.registerEquipment(VERSION, id);
 +    }
 +
 +    public static void register() {
@@ -19737,7 +23125,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    private V501() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V502.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V502.java
 new file mode 100644
@@ -19756,7 +23143,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V502 {
 +
-+    protected static final int VERSION = MCVersions.V16W20A + 1;
++    private static final int VERSION = MCVersions.V16W20A + 1;
 +
 +    public static void register() {
 +        ConverterAbstractItemRename.register(VERSION, (final String name) -> {
@@ -19789,7 +23176,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    private V502() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V505.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V505.java
 new file mode 100644
@@ -19806,7 +23192,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V505 {
 +
-+    protected static final int VERSION = MCVersions.V16W21B + 1;
++    private static final int VERSION = MCVersions.V16W21B + 1;
 +
 +    public static void register() {
 +        MCTypeRegistry.OPTIONS.addStructureConverter(new DataConverter<>(VERSION) {
@@ -19819,7 +23205,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    private V505() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V700.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V700.java
 new file mode 100644
@@ -19832,15 +23217,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
 +import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
-+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists;
 +import ca.spottedleaf.dataconverter.types.MapType;
 +
 +public final class V700 {
 +
-+    protected static final int VERSION = MCVersions.V1_10_2 + 188;
++    private static final int VERSION = MCVersions.V1_10_2 + 188;
 +
 +    private static void registerMob(final String id) {
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems"));
++        V100.registerEquipment(VERSION, id);
 +    }
 +
 +    public static void register() {
@@ -19871,15 +23255,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
 +import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
-+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists;
 +import ca.spottedleaf.dataconverter.types.MapType;
 +
 +public final class V701 {
 +
-+    protected static final int VERSION = MCVersions.V1_10_2 + 189;
++    private static final int VERSION = MCVersions.V1_10_2 + 189;
 +
 +    private static void registerMob(final String id) {
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems"));
++        V100.registerEquipment(VERSION, id);
 +    }
 +
 +    public static void register() {
@@ -19907,7 +23290,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    private V701() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V702.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V702.java
 new file mode 100644
@@ -19920,15 +23302,15 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.converters.DataConverter;
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
 +import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
-+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists;
++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils;
 +import ca.spottedleaf.dataconverter.types.MapType;
 +
 +public final class V702 {
 +
-+    protected static final int VERSION = MCVersions.V1_10_2 + 190;
++    private static final int VERSION = MCVersions.V1_10_2 + 190;
 +
 +    private static void registerMob(final String id) {
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems"));
++        V100.registerEquipment(VERSION, id);
 +    }
 +
 +    public static void register() {
@@ -19962,11 +23344,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        });
 +
 +        registerMob("ZombieVillager");
++        MCTypeRegistry.ENTITY.addWalker(VERSION, "ZombieVillager", (final MapType<String> root, final long fromVersion, final long toVersion) -> {
++            WalkerUtils.convertList(MCTypeRegistry.VILLAGER_TRADE, root.getMap("Offers"), "Recipes", fromVersion, toVersion);
++            return null;
++        });
 +        registerMob( "Husk");
 +    }
 +
 +    private V702() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V703.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V703.java
 new file mode 100644
@@ -19985,7 +23370,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V703 {
 +
-+    protected static final int VERSION = MCVersions.V1_10_2 + 191;
++    private static final int VERSION = MCVersions.V1_10_2 + 191;
 +
 +    public static void register() {
 +        MCTypeRegistry.ENTITY.addConverterForId("EntityHorse", new DataConverter<>(VERSION) {
@@ -20022,23 +23407,24 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        });
 +
 +        MCTypeRegistry.ENTITY.addWalker(VERSION, "Horse", new DataWalkerItems("ArmorItem", "SaddleItem"));
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, "Horse", new DataWalkerItemLists("ArmorItems", "HandItems"));
++        V100.registerEquipment(VERSION, "Horse");
 +
 +        MCTypeRegistry.ENTITY.addWalker(VERSION, "Donkey", new DataWalkerItems("SaddleItem"));
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, "Donkey", new DataWalkerItemLists("Items", "ArmorItems", "HandItems"));
++        MCTypeRegistry.ENTITY.addWalker(VERSION, "Donkey", new DataWalkerItemLists("Items"));
++        V100.registerEquipment(VERSION, "Donkey");
 +
 +        MCTypeRegistry.ENTITY.addWalker(VERSION, "Mule", new DataWalkerItems("SaddleItem"));
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, "Mule", new DataWalkerItemLists("Items", "ArmorItems", "HandItems"));
++        MCTypeRegistry.ENTITY.addWalker(VERSION, "Mule", new DataWalkerItemLists("Items"));
++        V100.registerEquipment(VERSION, "Mule");
 +
 +        MCTypeRegistry.ENTITY.addWalker(VERSION, "ZombieHorse", new DataWalkerItems("SaddleItem"));
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, "ZombieHorse", new DataWalkerItemLists("ArmorItems", "HandItems"));
++        V100.registerEquipment(VERSION, "ZombieHorse");
 +
 +        MCTypeRegistry.ENTITY.addWalker(VERSION, "SkeletonHorse", new DataWalkerItems("SaddleItem"));
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, "SkeletonHorse", new DataWalkerItemLists("ArmorItems", "HandItems"));
++        V100.registerEquipment(VERSION, "SkeletonHorse");
 +    }
 +
 +    private V703() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V704.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V704.java
 new file mode 100644
@@ -20052,12 +23438,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
 +import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
 +import ca.spottedleaf.dataconverter.minecraft.hooks.DataHookEnforceNamespacedID;
++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerTypePaths;
 +import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists;
 +import ca.spottedleaf.dataconverter.minecraft.walkers.item_name.DataWalkerItemNames;
 +import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems;
 +import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils;
 +import ca.spottedleaf.dataconverter.types.ObjectType;
 +import ca.spottedleaf.dataconverter.types.MapType;
++import ca.spottedleaf.dataconverter.util.Long2ObjectArraySortedMap;
 +import com.mojang.logging.LogUtils;
 +import net.minecraft.core.BlockPos;
 +import net.minecraft.core.registries.BuiltInRegistries;
@@ -20075,7 +23463,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +    private static final Logger LOGGER = LogUtils.getLogger();
 +
-+    protected static final int VERSION = MCVersions.V1_10_2 + 192;
++    private static final int VERSION = MCVersions.V1_10_2 + 192;
 +
 +    public static final Map<String, String> ITEM_ID_TO_TILE_ENTITY_ID = new HashMap<>() {
 +        @Override
@@ -20202,7 +23590,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:decorated_pot", "minecraft:decorated_pot");
 +        ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:crafter", "minecraft:crafter");
 +
-+        // These are missing from Vanilla (TODO check on update)
++        // These are missing from Vanilla up to 1.20.5
 +        ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:enchanting_table", "minecraft:enchanting_table");
 +        ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:comparator", "minecraft:comparator");
 +        ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:light_gray_bed", "minecraft:bed");
@@ -20230,11 +23618,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:suspicious_gravel", "minecraft:brushable_block");
 +        ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:calibrated_sculk_sensor", "minecraft:calibrated_sculk_sensor");
 +        ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:trial_spawner", "minecraft:trial_spawner");
++        ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:vault", "minecraft:vault");
 +    }
 +
 +    // This class is responsible for also integrity checking the item id to tile id map here, we just use the item registry to figure it out
-+
-+    static {
++    // No longer need to do this, as items now use components in 1.20.5
++    /*static {
 +        for (final Item item : BuiltInRegistries.ITEM) {
 +            if (!(item instanceof BlockItem)) {
 +                continue;
@@ -20271,9 +23660,59 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                }
 +            }
 +        }
++    }*/
++
++    private static Long2ObjectArraySortedMap<String> makeSingle(final long k1, final String v1) {
++        final Long2ObjectArraySortedMap<String> ret = new Long2ObjectArraySortedMap<>();
++
++        ret.put(k1, v1);
++
++        return ret;
 +    }
 +
-+    protected static final Map<String, String> TILE_ID_UPDATE = new HashMap<>();
++    private static Long2ObjectArraySortedMap<String> makeDouble(final long k1, final String v1,
++                                                                final long k2, final String v2) {
++        final Long2ObjectArraySortedMap<String> ret = new Long2ObjectArraySortedMap<>();
++
++        ret.put(k1, v1);
++        ret.put(k2, v2);
++
++        return ret;
++    }
++
++    private static final Map<String, Long2ObjectArraySortedMap<String>> ITEM_ID_TO_ENTITY_ID = new HashMap<>();
++    static {
++        ITEM_ID_TO_ENTITY_ID.put("minecraft:armor_stand", makeDouble(V99.VERSION, "ArmorStand", V705.VERSION, "minecraft:armor_stand"));
++        ITEM_ID_TO_ENTITY_ID.put("minecraft:painting", makeDouble(V99.VERSION, "Painting", V705.VERSION, "minecraft:painting"));
++        ITEM_ID_TO_ENTITY_ID.put("minecraft:boat", makeDouble(V99.VERSION, "Boat", V705.VERSION, "minecraft:boat"));
++        ITEM_ID_TO_ENTITY_ID.put("minecraft:oak_boat", makeSingle(V705.VERSION, "minecraft:boat"));
++        ITEM_ID_TO_ENTITY_ID.put("minecraft:oak_chest_boat", makeSingle(V705.VERSION, "minecraft:chest_boat"));
++        ITEM_ID_TO_ENTITY_ID.put("minecraft:spruce_boat",makeSingle(V705.VERSION, "minecraft:boat"));
++        ITEM_ID_TO_ENTITY_ID.put("minecraft:spruce_chest_boat", makeSingle(V705.VERSION, "minecraft:chest_boat"));
++        ITEM_ID_TO_ENTITY_ID.put("minecraft:birch_boat", makeSingle(V705.VERSION, "minecraft:boat"));
++        ITEM_ID_TO_ENTITY_ID.put("minecraft:birch_chest_boat", makeSingle(V705.VERSION, "minecraft:chest_boat"));
++        ITEM_ID_TO_ENTITY_ID.put("minecraft:jungle_boat", makeSingle(V705.VERSION, "minecraft:boat"));
++        ITEM_ID_TO_ENTITY_ID.put("minecraft:jungle_chest_boat", makeSingle(V705.VERSION, "minecraft:chest_boat"));
++        ITEM_ID_TO_ENTITY_ID.put("minecraft:acacia_boat", makeSingle(V705.VERSION, "minecraft:boat"));
++        ITEM_ID_TO_ENTITY_ID.put("minecraft:acacia_chest_boat", makeSingle(V705.VERSION, "minecraft:chest_boat"));
++        ITEM_ID_TO_ENTITY_ID.put("minecraft:cherry_boat", makeSingle(V705.VERSION, "minecraft:boat"));
++        ITEM_ID_TO_ENTITY_ID.put("minecraft:cherry_chest_boat", makeSingle(V705.VERSION, "minecraft:chest_boat"));
++        ITEM_ID_TO_ENTITY_ID.put("minecraft:dark_oak_boat", makeSingle(V705.VERSION, "minecraft:boat"));
++        ITEM_ID_TO_ENTITY_ID.put("minecraft:dark_oak_chest_boat", makeSingle(V705.VERSION, "minecraft:chest_boat"));
++        ITEM_ID_TO_ENTITY_ID.put("minecraft:mangrove_boat", makeSingle(V705.VERSION, "minecraft:boat"));
++        ITEM_ID_TO_ENTITY_ID.put("minecraft:mangrove_chest_boat", makeSingle(V705.VERSION, "minecraft:chest_boat"));
++        ITEM_ID_TO_ENTITY_ID.put("minecraft:bamboo_raft", makeSingle(V705.VERSION, "minecraft:boat"));
++        ITEM_ID_TO_ENTITY_ID.put("minecraft:bamboo_chest_raft", makeSingle(V705.VERSION, "minecraft:chest_boat"));
++        ITEM_ID_TO_ENTITY_ID.put("minecraft:minecart", makeDouble(V99.VERSION, "MinecartRideable", V705.VERSION, "minecraft:minecart"));
++        ITEM_ID_TO_ENTITY_ID.put("minecraft:chest_minecart", makeDouble(V99.VERSION, "MinecartChest", V705.VERSION, "minecraft:chest_minecart"));
++        ITEM_ID_TO_ENTITY_ID.put("minecraft:furnace_minecart", makeDouble(V99.VERSION, "MinecartFurnace", V705.VERSION, "minecraft:furnace_minecart"));
++        ITEM_ID_TO_ENTITY_ID.put("minecraft:tnt_minecart", makeDouble(V99.VERSION, "MinecartTNT", V705.VERSION, "minecraft:tnt_minecart"));
++        ITEM_ID_TO_ENTITY_ID.put("minecraft:hopper_minecart", makeDouble(V99.VERSION, "MinecartHopper", V705.VERSION, "minecraft:hopper_minecart"));
++        ITEM_ID_TO_ENTITY_ID.put("minecraft:item_frame", makeDouble(V99.VERSION, "ItemFrame", V705.VERSION, "minecraft:item_frame"));
++        ITEM_ID_TO_ENTITY_ID.put("minecraft:glow_item_frame", makeSingle(V705.VERSION, "minecraft:glow_item_frame"));
++    }
++
++    private static final Map<String, String> TILE_ID_UPDATE = new HashMap<>();
 +    static {
 +        TILE_ID_UPDATE.put("Airportal", "minecraft:end_portal");
 +        TILE_ID_UPDATE.put("Banner", "minecraft:banner");
@@ -20300,7 +23739,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        TILE_ID_UPDATE.put("Trap", "minecraft:dispenser");
 +    }
 +
-+    protected static void registerInventory(final String id) {
++    private static void registerInventory(final String id) {
 +        MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("Items"));
 +    }
 +
@@ -20319,7 +23758,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        });
 +
 +
-+
++        MCTypeRegistry.TILE_ENTITY.addStructureWalker(VERSION, (final MapType<String> data, final long fromVersion, final long toVersion) -> {
++            WalkerUtils.convert(MCTypeRegistry.DATA_COMPONENTS, data, "components", fromVersion, toVersion);
++            return null;
++        });
 +        registerInventory( "minecraft:furnace");
 +        registerInventory( "minecraft:chest");
 +        MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:jukebox", new DataWalkerItems("RecordItem"));
@@ -20332,6 +23774,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        registerInventory("minecraft:brewing_stand");
 +        registerInventory("minecraft:hopper");
 +        MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:flower_pot", new DataWalkerItemNames("Item"));
++        MCTypeRegistry.TILE_ENTITY.addWalker(
++                VERSION, "minecraft:command_block",
++                new DataWalkerTypePaths<>(MCTypeRegistry.DATACONVERTER_CUSTOM_TYPE_COMMAND, "Command")
++        );
 +
 +        MCTypeRegistry.ITEM_STACK.addStructureWalker(VERSION, (final MapType<String> data, final long fromVersion, final long toVersion) -> {
 +            WalkerUtils.convert(MCTypeRegistry.ITEM_NAME, data, "id", fromVersion, toVersion);
@@ -20344,43 +23790,29 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            // only things here are in tag, if changed update if above
 +
 +            WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, tag, "Items", fromVersion, toVersion);
++            WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, tag, "ChargedProjectiles", fromVersion, toVersion);
 +
 +            MapType<String> entityTag = tag.getMap("EntityTag");
 +            if (entityTag != null) {
 +                final String itemId = data.getString("id");
 +                final String entityId;
-+                if ("minecraft:armor_stand".equals(itemId)) {
-+                    // The check for version id is changed here. For whatever reason, the legacy
-+                    // data converters used entity id "minecraft:armor_stand" when version was greater-than 514,
-+                    // but entity ids were not namespaced until V705! So somebody fucked up the legacy converters.
-+                    // DFU agrees with my analysis here, it will only set the entityId here to the namespaced variant
-+                    // with the V705 schema.
-+                    entityId = DataConverter.getVersion(fromVersion) < 705 ? "ArmorStand" : "minecraft:armor_stand";
-+                } else if (itemId != null && itemId.contains("_spawn_egg")) {
++                if (itemId != null && itemId.contains("_spawn_egg")) {
 +                    // V1451 changes spawn eggs to have the sub entity id be a part of the item id, but of course Mojang never
 +                    // bothered to write in logic to set the sub entity id, so we have to.
 +                    // format is ALWAYS <namespace>:<id>_spawn_egg post flattening
 +                    entityId = itemId.substring(0, itemId.indexOf("_spawn_egg"));
-+                } else if ("minecraft:item_frame".equals(itemId)) {
-+                    // add missing item_frame entity id
-+                    // version check is same for armorstand, as both were namespaced at the same time
-+                    entityId = DataConverter.getVersion(fromVersion) < 705 ? "ItemFrame" : "minecraft:item_frame";
-+                } else if ("minecraft:glow_item_frame".equals(itemId)) {
-+                    // add missing glow_item_frame entity id
-+                    entityId = "minecraft:glow_item_frame";
 +                } else {
-+                    entityId = entityTag.getString("id");
++                    final Long2ObjectArraySortedMap<String> mappingByVersion = ITEM_ID_TO_ENTITY_ID.get(itemId);
++                    final String mapped = mappingByVersion == null ? null : mappingByVersion.getFloor(fromVersion);
++                    entityId = mapped == null ? entityTag.getString("id") : mapped;
 +                }
 +
-+                final boolean removeId;
 +                if (entityId == null) {
 +                    if (!"minecraft:air".equals(itemId)) {
 +                        LOGGER.warn("Unable to resolve Entity for ItemStack (V704): " + itemId);
 +                    }
-+                    removeId = false;
 +                } else {
-+                    removeId = !entityTag.hasKey("id", ObjectType.STRING);
-+                    if (removeId) {
++                    if (!entityTag.hasKey("id", ObjectType.STRING)) {
 +                        entityTag.setString("id", entityId);
 +                    }
 +                }
@@ -20391,9 +23823,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                    entityTag = replace;
 +                    tag.setMap("EntityTag", entityTag);
 +                }
-+                if (removeId) {
-+                    entityTag.remove("id");
-+                }
 +            }
 +
 +            MapType<String> blockEntityTag = tag.getMap("BlockEntityTag");
@@ -20406,25 +23835,20 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                } else {
 +                    entityId = ITEM_ID_TO_TILE_ENTITY_ID.get(itemId);
 +                }
-+                final boolean removeId;
++
 +                if (entityId == null) {
 +                    if (!"minecraft:air".equals(itemId)) {
 +                        LOGGER.warn("Unable to resolve BlockEntity for ItemStack (V704): " + itemId);
 +                    }
-+                    removeId = false;
 +                } else {
-+                    removeId = !blockEntityTag.hasKey("id", ObjectType.STRING);
-+                    if (removeId) {
++                    if (!blockEntityTag.hasKey("id", ObjectType.STRING)) {
 +                        blockEntityTag.setString("id", entityId);
 +                    }
 +                }
 +                final MapType<String> replace = MCTypeRegistry.TILE_ENTITY.convert(blockEntityTag, fromVersion, toVersion);
 +                if (replace != null) {
 +                    blockEntityTag = replace;
-+                    tag.setMap("BlockEntityTag", entityTag);
-+                }
-+                if (removeId) {
-+                    blockEntityTag.remove("id");
++                    tag.setMap("BlockEntityTag", blockEntityTag);
 +                }
 +            }
 +
@@ -20454,25 +23878,20 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.minecraft.hooks.DataHookEnforceNamespacedID;
 +import ca.spottedleaf.dataconverter.minecraft.hooks.DataHookValueTypeEnforceNamespaced;
 +import ca.spottedleaf.dataconverter.minecraft.walkers.block_name.DataWalkerBlockNames;
++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerTypePaths;
 +import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists;
 +import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems;
 +import ca.spottedleaf.dataconverter.minecraft.walkers.tile_entity.DataWalkerTileEntities;
 +import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils;
-+import ca.spottedleaf.dataconverter.types.ObjectType;
-+import ca.spottedleaf.dataconverter.types.ListType;
 +import ca.spottedleaf.dataconverter.types.MapType;
-+import com.mojang.logging.LogUtils;
-+import org.slf4j.Logger;
 +import java.util.HashMap;
 +import java.util.Map;
 +
 +public final class V705 {
 +
-+    private static final Logger LOGGER = LogUtils.getLogger();
++    public static final int VERSION = MCVersions.V1_10_2 + 193;
 +
-+    protected static final int VERSION = MCVersions.V1_10_2 + 193;
-+
-+    protected static final Map<String, String> ENTITY_ID_UPDATE = new HashMap<>();
++    private static final Map<String, String> ENTITY_ID_UPDATE = new HashMap<>();
 +    static {
 +        ENTITY_ID_UPDATE.put("AreaEffectCloud", "minecraft:area_effect_cloud");
 +        ENTITY_ID_UPDATE.put("ArmorStand", "minecraft:armor_stand");
@@ -20552,7 +23971,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    private static void registerMob(final String id) {
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems"));
++        V100.registerEquipment(VERSION, id);
 +    }
 +
 +    private static void registerThrowableProjectile(final String id) {
@@ -20562,6 +23981,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    public static void register() {
 +        ConverterAbstractEntityRename.register(VERSION, ENTITY_ID_UPDATE::get);
 +
++        MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:area_effect_cloud", new DataWalkerTypePaths<>(MCTypeRegistry.PARTICLE, "Particle"));
 +        registerMob("minecraft:armor_stand");
 +        MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:arrow", new DataWalkerBlockNames("inTile"));
 +        registerMob("minecraft:bat");
@@ -20573,12 +23993,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:commandblock_minecart", new DataWalkerBlockNames("DisplayTile"));
 +        registerMob("minecraft:cow");
 +        registerMob("minecraft:creeper");
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:donkey", new DataWalkerItemLists("Items", "ArmorItems", "HandItems"));
++        MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:donkey", new DataWalkerItemLists("Items"));
++        V100.registerEquipment(VERSION, "minecraft:donkey");
 +        MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:donkey", new DataWalkerItems("SaddleItem"));
 +        registerThrowableProjectile("minecraft:egg");
 +        registerMob("minecraft:elder_guardian");
 +        registerMob("minecraft:ender_dragon");
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:enderman", new DataWalkerItemLists("ArmorItems", "HandItems"));
++        V100.registerEquipment(VERSION, "minecraft:enderman");
 +        MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:enderman", new DataWalkerBlockNames("carried"));
 +        registerMob("minecraft:endermite");
 +        registerThrowableProjectile("minecraft:ender_pearl");
@@ -20593,14 +24014,15 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:hopper_minecart", new DataWalkerBlockNames("DisplayTile"));
 +        MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:hopper_minecart", new DataWalkerItemLists("Items"));
 +        MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:horse", new DataWalkerItems("ArmorItem", "SaddleItem"));
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:horse", new DataWalkerItemLists("ArmorItems", "HandItems"));
++        V100.registerEquipment(VERSION, "minecraft:horse");
 +        registerMob("minecraft:husk");
 +        MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:item", new DataWalkerItems("Item"));
 +        MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:item_frame", new DataWalkerItems("Item"));
 +        registerMob("minecraft:magma_cube");
 +        MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:minecart", new DataWalkerBlockNames("DisplayTile"));
 +        registerMob("minecraft:mooshroom");
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:mule", new DataWalkerItemLists("Items", "ArmorItems", "HandItems"));
++        MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:mule", new DataWalkerItemLists("Items"));
++        V100.registerEquipment(VERSION, "minecraft:mule");
 +        MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:mule", new DataWalkerItems("SaddleItem"));
 +        registerMob("minecraft:ocelot");
 +        registerMob("minecraft:pig");
@@ -20612,7 +24034,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        registerMob("minecraft:shulker");
 +        registerMob("minecraft:silverfish");
 +        registerMob("minecraft:skeleton");
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:skeleton_horse", new DataWalkerItemLists("ArmorItems", "HandItems"));
++        V100.registerEquipment(VERSION, "minecraft:skeleton_horse");
 +        MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:skeleton_horse", new DataWalkerItems("SaddleItem"));
 +        registerMob("minecraft:slime");
 +        registerThrowableProjectile("minecraft:small_fireball");
@@ -20631,24 +24053,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:villager", (final MapType<String> data, final long fromVersion, final long toVersion) -> {
 +            WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "Inventory", fromVersion, toVersion);
 +
-+            final MapType<String> offers = data.getMap("Offers");
-+            if (offers != null) {
-+                final ListType recipes = offers.getList("Recipes", ObjectType.MAP);
-+                if (recipes != null) {
-+                    for (int i = 0, len = recipes.size(); i < len; ++i) {
-+                        final MapType<String> recipe = recipes.getMap(i);
-+                        WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "buy", fromVersion, toVersion);
-+                        WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "buyB", fromVersion, toVersion);
-+                        WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "sell", fromVersion, toVersion);
-+                    }
-+                }
-+            }
-+
-+            WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "ArmorItems", fromVersion, toVersion);
-+            WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "HandItems", fromVersion, toVersion);
++            WalkerUtils.convertList(MCTypeRegistry.VILLAGER_TRADE, data.getMap("Offers"), "Recipes", fromVersion, toVersion);
 +
 +            return null;
 +        });
++        V100.registerEquipment(VERSION, "minecraft:villager");
 +        registerMob("minecraft:villager_golem");
 +        registerMob("minecraft:witch");
 +        registerMob("minecraft:wither");
@@ -20658,11 +24067,17 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        registerThrowableProjectile("minecraft:xp_bottle");
 +        registerMob("minecraft:zombie");
 +        MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:zombie_horse", new DataWalkerItems("SaddleItem"));
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:zombie_horse", new DataWalkerItemLists("ArmorItems", "HandItems"));
++        V100.registerEquipment(VERSION, "minecraft:zombie_horse");
 +        registerMob("minecraft:zombie_pigman");
 +        registerMob("minecraft:zombie_villager");
++        MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:zombie_villager", (final MapType<String> data, final long fromVersion, final long toVersion) -> {
++            WalkerUtils.convertList(MCTypeRegistry.VILLAGER_TRADE, data.getMap("Offers"), "Recipes", fromVersion, toVersion);
++
++            return null;
++        });
 +        registerMob("minecraft:evocation_illager");
-+        MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:llama", new DataWalkerItemLists("Items", "ArmorItems", "HandItems"));
++        MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:llama", new DataWalkerItemLists("Items"));
++        V100.registerEquipment(VERSION, "minecraft:llama");
 +        MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:llama", new DataWalkerItems("SaddleItem", "DecorItem"));
 +        registerMob("minecraft:vex");
 +        registerMob("minecraft:vindication_illager");
@@ -20675,7 +24090,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    private V705() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V804.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V804.java
 new file mode 100644
@@ -20694,7 +24108,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V804 {
 +
-+    protected static final int VERSION = MCVersions.V16W35A + 1;
++    private static final int VERSION = MCVersions.V16W35A + 1;
 +
 +    public static void register() {
 +        MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:banner", new DataConverter<>(VERSION) {
@@ -20741,7 +24155,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    private V804() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V806.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V806.java
 new file mode 100644
@@ -20760,7 +24173,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V806 {
 +
-+    protected static final int VERSION = MCVersions.V16W36A + 1;
++    private static final int VERSION = MCVersions.V16W36A + 1;
 +
 +    public static void register() {
 +        final DataConverter<MapType<String>, MapType<String>> potionWaterUpdater = new DataConverter<>(VERSION) {
@@ -20787,7 +24200,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    private V806() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V808.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V808.java
 new file mode 100644
@@ -20806,7 +24218,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V808 {
 +
-+    protected static final int VERSION = MCVersions.V16W38A + 1;
++    private static final int VERSION = MCVersions.V16W38A + 1;
 +
 +    public static void register() {
 +        MCTypeRegistry.ENTITY.addConverterForId("minecraft:shulker", new DataConverter<>(VERSION, 1) {
@@ -20823,7 +24235,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    private V808() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V813.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V813.java
 new file mode 100644
@@ -20840,9 +24251,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V813 {
 +
-+    protected static final int VERSION = MCVersions.V16W40A;
++    private static final int VERSION = MCVersions.V16W40A;
 +
-+    public static final String[] SHULKER_ID_BY_COLOUR = new String[] {
++    private static final String[] SHULKER_ID_BY_COLOUR = new String[] {
 +            "minecraft:white_shulker_box",
 +            "minecraft:orange_shulker_box",
 +            "minecraft:magenta_shulker_box",
@@ -20911,7 +24322,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +public final class V816 {
 +
-+    protected static final int VERSION = MCVersions.V16W43A;
++    private static final int VERSION = MCVersions.V16W43A;
 +
 +    public static void register() {
 +        MCTypeRegistry.OPTIONS.addStructureConverter(new DataConverter<>(VERSION) {
@@ -20927,7 +24338,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    private V816() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V820.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V820.java
 new file mode 100644
@@ -20940,19 +24350,21 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.minecraft.MCVersions;
 +import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename;
 +import com.google.common.collect.ImmutableMap;
++import java.util.HashMap;
 +
 +public final class V820 {
 +
-+    protected static final int VERSION = MCVersions.V1_11 + 1;
++    private static final int VERSION = MCVersions.V1_11 + 1;
 +
 +    public static void register() {
-+        ConverterAbstractItemRename.register(VERSION, ImmutableMap.of(
-+                "minecraft:totem", "minecraft:totem_of_undying"
++        ConverterAbstractItemRename.register(VERSION, new HashMap<>(
++                ImmutableMap.of(
++                        "minecraft:totem", "minecraft:totem_of_undying"
++                )
 +        )::get);
 +    }
 +
 +    private V820() {}
-+
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V99.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V99.java
 new file mode 100644
@@ -20968,6 +24380,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.minecraft.hooks.DataHookEnforceNamespacedID;
 +import ca.spottedleaf.dataconverter.minecraft.hooks.DataHookValueTypeEnforceNamespaced;
 +import ca.spottedleaf.dataconverter.minecraft.walkers.block_name.DataWalkerBlockNames;
++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerTypePaths;
 +import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists;
 +import ca.spottedleaf.dataconverter.minecraft.walkers.item_name.DataWalkerItemNames;
 +import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems;
@@ -20987,10 +24400,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +    private static final Logger LOGGER = LogUtils.getLogger();
 +
-+    protected static final int VERSION = MCVersions.V15W32A - 1;
-+
-+    protected static final Map<String, String> ITEM_ID_TO_TILE_ENTITY_ID = new HashMap<>();
++    public static final int VERSION = MCVersions.V15W32A - 1;
 +
++    private static final Map<String, String> ITEM_ID_TO_TILE_ENTITY_ID = new HashMap<>();
 +    static {
 +        ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:furnace", "Furnace");
 +        ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:lit_furnace", "Furnace");
@@ -21123,25 +24535,19 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        registerMob("Rabbit");
 +        MCTypeRegistry.ENTITY.addWalker(VERSION, "Villager", new DataWalkerItemLists("Inventory", "Equipment"));
 +        MCTypeRegistry.ENTITY.addWalker(VERSION, "Villager", (final MapType<String> data, final long fromVersion, final long toVersion) -> {
-+            final MapType<String> offers = data.getMap("Offers");
-+            if (offers != null) {
-+                final ListType recipes = offers.getList("Recipes", ObjectType.MAP);
-+                if (recipes != null) {
-+                    for (int i = 0; i < recipes.size(); ++i) {
-+                        final MapType<String> recipe = recipes.getMap(i);
-+
-+                        WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "buy", fromVersion, toVersion);
-+                        WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "buyB", fromVersion, toVersion);
-+                        WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "sell", fromVersion, toVersion);
-+                    }
-+                }
-+            }
++            WalkerUtils.convertList(MCTypeRegistry.VILLAGER_TRADE, data.getMap("Offers"), "Recipes", fromVersion, toVersion);
 +
 +            return null;
 +        });
 +        registerMob("Shulker");
++        MCTypeRegistry.ENTITY.addWalker(VERSION, "AreaEffectCloud", new DataWalkerTypePaths<>(MCTypeRegistry.PARTICLE, "Particle"));
 +
 +        // tile entities
++        MCTypeRegistry.TILE_ENTITY.addStructureWalker(VERSION, (final MapType<String> data, final long fromVersion, final long toVersion) -> {
++            WalkerUtils.convert(MCTypeRegistry.DATA_COMPONENTS, data, "components", fromVersion, toVersion);
++
++            return null;
++        });
 +
 +        // Inventory -> new DataWalkerItemLists("Items")
 +        registerInventory("Furnace");
@@ -21155,8 +24561,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        });
 +        registerInventory("Cauldron");
 +        registerInventory("Hopper");
-+        // Note: Vanilla does not properly handle this case, it will not convert int ids!
++        // Note: Vanilla does not properly handle this case for FlowerPot, it will not convert int ids!
 +        MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "FlowerPot", new DataWalkerItemNames("Item"));
++        MCTypeRegistry.TILE_ENTITY.addWalker(
++                VERSION, "Control",
++                new DataWalkerTypePaths<>(MCTypeRegistry.DATACONVERTER_CUSTOM_TYPE_COMMAND, "Command")
++        );
 +
 +        // rest
 +
@@ -21171,6 +24581,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            // only things here are in tag, if changed update if above
 +
 +            WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, tag, "Items", fromVersion, toVersion);
++            WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, tag, "ChargedProjectiles", fromVersion, toVersion);
 +
 +            MapType<String> entityTag = tag.getMap("EntityTag");
 +            if (entityTag != null) {
@@ -21186,6 +24597,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +                } else if ("minecraft:item_frame".equals(itemId)) {
 +                    // add missing item_frame entity id
 +                    entityId = "ItemFrame";
++                } else if ("minecraft:painting".equals(itemId)) {
++                    entityId = "Painting";
 +                } else {
 +                    entityId = entityTag.getString("id");
 +                }
@@ -21294,6 +24707,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            return null;
 +        });
 +
++        MCTypeRegistry.VILLAGER_TRADE.addStructureWalker(VERSION, (final MapType<String> root, final long fromVersion, final long toVersion) -> {
++            WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, root, "buy", fromVersion, toVersion);
++            WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, root, "buyB", fromVersion, toVersion);
++            WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, root, "sell", fromVersion, toVersion);
++
++            return null;
++        });
 +
 +        // Enforce namespacing for ids
 +        MCTypeRegistry.BLOCK_NAME.addStructureHook(VERSION, new DataHookValueTypeEnforceNamespaced());
@@ -21303,7 +24723,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        // Entity is absent; the String form is not yet namespaced, unlike the above.
 +    }
 +
-+    protected static String getStringId(final Object id) {
++    private static String getStringId(final Object id) {
 +        if (id instanceof String) {
 +            return (String)id;
 +        } else if (id instanceof Number) {
@@ -21313,9 +24733,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        }
 +    }
 +
-+    private V99() {
-+        throw new RuntimeException();
-+    }
++    private V99() {}
 +}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/block_name/DataWalkerBlockNames.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/block_name/DataWalkerBlockNames.java
 new file mode 100644
@@ -21347,7 +24765,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils;
 +import ca.spottedleaf.dataconverter.types.MapType;
 +
-+public final class GameEventListenerWalker implements DataWalker<String> {
++public final class GameEventListenerWalker implements DataWalker<MapType<String>> {
 +
 +    @Override
 +    public MapType<String> walk(final MapType<String> data, final long fromVersion, final long toVersion) {
@@ -21379,7 +24797,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.types.ListType;
 +import ca.spottedleaf.dataconverter.types.MapType;
 +
-+public class DataWalkerListPaths<T, R> implements DataWalker<String> {
++public class DataWalkerListPaths<T, R> implements DataWalker<MapType<String>> {
 +
 +    protected final DataType<T, R> type;
 +    protected final String[] paths;
@@ -21422,7 +24840,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import ca.spottedleaf.dataconverter.converters.datatypes.DataWalker;
 +import ca.spottedleaf.dataconverter.types.MapType;
 +
-+public class DataWalkerTypePaths<T, R> implements DataWalker<String> {
++public class DataWalkerTypePaths<T, R> implements DataWalker<MapType<String>> {
 +
 +    protected final DataType<T, R> type;
 +    protected final String[] paths;
@@ -21498,6 +24916,34 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        }
 +    }
 +
++    public static void convertListPath(final MCDataType type, final MapType<String> data, final String listPath, final String elementPath,
++                                       final long fromVersion, final long toVersion) {
++        if (data == null) {
++            return;
++        }
++
++        final ListType list = data.getList(listPath, ObjectType.MAP);
++        if (list != null) {
++            for (int i = 0, len = list.size(); i < len; ++i) {
++                WalkerUtils.convert(type, list.getMap(i), elementPath, fromVersion, toVersion);
++            }
++        }
++    }
++
++    public static void convertListPath(final MCDataType type, final MapType<String> data, final String listPath, final String elementPath1,
++                                       final String elementPath2, final long fromVersion, final long toVersion) {
++        if (data == null) {
++            return;
++        }
++
++        final ListType list = data.getList(listPath, ObjectType.MAP);
++        if (list != null) {
++            for (int i = 0, len = list.size(); i < len; ++i) {
++                WalkerUtils.convert(type, list.getMap(i).getMap(elementPath1), elementPath2, fromVersion, toVersion);
++            }
++        }
++    }
++
 +    public static void convert(final MCValueType type, final MapType<String> data, final String path, final long fromVersion, final long toVersion) {
 +        if (data == null) {
 +            return;
@@ -21537,6 +24983,34 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        }
 +    }
 +
++    public static void convertListPath(final MCValueType type, final MapType<String> data, final String listPath, final String elementPath,
++                                       final long fromVersion, final long toVersion) {
++        if (data == null) {
++            return;
++        }
++
++        final ListType list = data.getList(listPath, ObjectType.MAP);
++        if (list != null) {
++            for (int i = 0, len = list.size(); i < len; ++i) {
++                WalkerUtils.convert(type, list.getMap(i), elementPath, fromVersion, toVersion);
++            }
++        }
++    }
++
++    public static void convertListPath(final MCValueType type, final MapType<String> data, final String listPath, final String elementPath1,
++                                       final String elementPath2, final long fromVersion, final long toVersion) {
++        if (data == null) {
++            return;
++        }
++
++        final ListType list = data.getList(listPath, ObjectType.MAP);
++        if (list != null) {
++            for (int i = 0, len = list.size(); i < len; ++i) {
++                WalkerUtils.convert(type, list.getMap(i).getMap(elementPath1), elementPath2, fromVersion, toVersion);
++            }
++        }
++    }
++
 +    public static void convertKeys(final MCValueType type, final MapType<String> data, final String path, final long fromVersion, final long toVersion) {
 +        if (data == null) {
 +            return;
@@ -22104,6 +25578,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +    public String getString(final K key, final String dfl);
 +
++    public String getForcedString(final K key);
++
++    public String getForcedString(final K key, final String dfl);
++
 +    public void setString(final K key, final String val);
 +
 +    public default void setGeneric(final K key, final Object value) {
@@ -23144,6 +26622,30 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    @Override
++    public String getForcedString(final String key) {
++        return this.getForcedString(key, null);
++    }
++
++    @Override
++    public String getForcedString(final String key, final String dfl) {
++        final JsonElement element = this.map.get(key);
++        if (element instanceof JsonPrimitive) {
++            final JsonPrimitive primitive = (JsonPrimitive)element;
++            if (primitive.isString()) {
++                return primitive.getAsString();
++            } else if (primitive.isNumber()) {
++                return primitive.getAsString();
++            }
++
++            return primitive.toString();
++        } else if (element != null) {
++            return element.toString();
++        }
++
++        return dfl;
++    }
++
++    @Override
 +    public void setString(final String key, final String val) {
 +        this.map.addProperty(key, val);
 +    }
@@ -24147,6 +27649,20 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    }
 +
 +    @Override
++    public String getForcedString(final String key) {
++        return this.getForcedString(key, null);
++    }
++
++    @Override
++    public String getForcedString(final String key, final String dfl) {
++        final Tag tag = this.map.get(key);
++        if (tag != null) {
++            return tag.getAsString();
++        }
++        return dfl;
++    }
++
++    @Override
 +    public void setString(final String key, final String val) {
 +        this.map.putString(key, val);
 +    }
@@ -24175,6 +27691,447 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        return new NBTMapType();
 +    }
 +}
+diff --git a/src/main/java/ca/spottedleaf/dataconverter/util/CommandArgumentUpgrader.java b/src/main/java/ca/spottedleaf/dataconverter/util/CommandArgumentUpgrader.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/dataconverter/util/CommandArgumentUpgrader.java
+@@ -0,0 +0,0 @@
++package ca.spottedleaf.dataconverter.util;
++
++import ca.spottedleaf.dataconverter.minecraft.MCDataConverter;
++import ca.spottedleaf.dataconverter.minecraft.MCVersions;
++import ca.spottedleaf.dataconverter.minecraft.converters.custom.V3818_Commands;
++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
++import com.google.gson.JsonElement;
++import com.google.gson.JsonParseException;
++import com.google.gson.internal.Streams;
++import com.google.gson.stream.JsonReader;
++import com.mojang.brigadier.CommandDispatcher;
++import com.mojang.brigadier.LiteralMessage;
++import com.mojang.brigadier.ParseResults;
++import com.mojang.brigadier.StringReader;
++import com.mojang.brigadier.arguments.ArgumentType;
++import com.mojang.brigadier.context.CommandContextBuilder;
++import com.mojang.brigadier.context.ParsedArgument;
++import com.mojang.brigadier.context.StringRange;
++import com.mojang.brigadier.exceptions.CommandSyntaxException;
++import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
++import com.mojang.brigadier.tree.ArgumentCommandNode;
++import com.mojang.brigadier.tree.CommandNode;
++import com.mojang.brigadier.tree.LiteralCommandNode;
++import com.mojang.serialization.Lifecycle;
++import it.unimi.dsi.fastutil.Pair;
++import java.lang.reflect.Field;
++import java.util.ArrayList;
++import java.util.HashMap;
++import java.util.LinkedHashMap;
++import java.util.List;
++import java.util.Map;
++import java.util.Optional;
++import java.util.function.BiFunction;
++import java.util.function.Consumer;
++import java.util.function.Function;
++import java.util.stream.Stream;
++import net.minecraft.SharedConstants;
++import net.minecraft.Util;
++import net.minecraft.commands.CommandBuildContext;
++import net.minecraft.commands.CommandSource;
++import net.minecraft.commands.CommandSourceStack;
++import net.minecraft.commands.Commands;
++import net.minecraft.commands.arguments.ComponentArgument;
++import net.minecraft.commands.arguments.item.ItemArgument;
++import net.minecraft.core.Holder;
++import net.minecraft.core.HolderLookup;
++import net.minecraft.core.HolderSet;
++import net.minecraft.core.Registry;
++import net.minecraft.nbt.CompoundTag;
++import net.minecraft.nbt.Tag;
++import net.minecraft.nbt.TagParser;
++import net.minecraft.network.chat.CommonComponents;
++import net.minecraft.resources.ResourceKey;
++import net.minecraft.resources.ResourceLocation;
++import net.minecraft.server.commands.ExecuteCommand;
++import net.minecraft.server.commands.ReturnCommand;
++import net.minecraft.tags.TagKey;
++import net.minecraft.util.GsonHelper;
++import net.minecraft.world.phys.Vec2;
++import net.minecraft.world.phys.Vec3;
++import org.jetbrains.annotations.Nullable;
++
++public final class CommandArgumentUpgrader {
++    private final CommandDispatcher<CommandSourceStack> dispatcher;
++    private final CommandBuildContext context;
++    private final CommandSourceStack source;
++    private final Map<Class<?>, BiFunction<ArgumentType<?>, CommandBuildContext, ArgumentType<?>>> replacements;
++
++    public static CommandArgumentUpgrader upgrader_1_20_4_to_1_20_5(final int functionPermissionLevel) {
++        return new CommandArgumentUpgrader(functionPermissionLevel, builder -> {
++            builder.registerReplacement(ItemArgument.class, (argument, ctx) -> new ItemParser_1_20_4());
++            builder.registerReplacement(ComponentArgument.class, (argument, ctx) -> new ComponentParser_1_20_4());
++        });
++    }
++
++    public CommandArgumentUpgrader(
++        final int functionPermissionLevel,
++        final Consumer<ReplacementsBuilder> consumer
++    ) {
++        this(
++            new Commands(Commands.CommandSelection.DEDICATED, makeDummyCommandBuildContext()).getDispatcher(),
++            functionPermissionLevel,
++            consumer
++        );
++    }
++
++    private CommandArgumentUpgrader(
++        final CommandDispatcher<CommandSourceStack> dispatcher,
++        final int functionPermissionLevel,
++        final Consumer<ReplacementsBuilder> consumer
++    ) {
++        final ReplacementsBuilder builder = new ReplacementsBuilder();
++        consumer.accept(builder);
++        this.replacements = Map.copyOf(builder.replacements);
++
++        final CommandBuildContext context = makeDummyCommandBuildContext();
++        this.dispatcher = new CommandDispatcher<>();
++        this.context = context;
++        final List<CommandNode<CommandSourceStack>> aliases = new ArrayList<>();
++        for (final CommandNode<CommandSourceStack> child : dispatcher.getRoot().getChildren()) {
++            final CopyResult result = this.copyCommand(this.dispatcher.getRoot(), child, null);
++            if (result.replaced()) {
++                this.dispatcher.getRoot().addChild(result.root);
++            }
++            aliases.addAll(result.aliases);
++        }
++        aliases.forEach(redirectNode -> {
++            final CommandNode<CommandSourceStack> toNode = this.dispatcher.getRoot()
++                .getChild(redirectNode.getRedirect().getName());
++            if (toNode != null) {
++                this.dispatcher.getRoot().addChild(
++                    new LiteralCommandNode<>(
++                        redirectNode.getName(),
++                        null,
++                        null,
++                        toNode,
++                        redirectNode.getRedirectModifier(),
++                        redirectNode.isFork()
++                    )
++                );
++            }
++        });
++        ExecuteCommand.register(this.dispatcher, context);
++        ReturnCommand.register(this.dispatcher);
++        // This looks weird, but it's what vanilla does when loading functions for datapacks
++        this.source = new CommandSourceStack(
++            CommandSource.NULL,
++            Vec3.ZERO,
++            Vec2.ZERO,
++            null,
++            functionPermissionLevel,
++            "",
++            CommonComponents.EMPTY,
++            null,
++            null
++        );
++    }
++
++    public static final class ReplacementsBuilder {
++        private final Map<Class<?>, BiFunction<ArgumentType<?>, CommandBuildContext, ArgumentType<?>>> replacements =
++            new HashMap<>();
++
++        private ReplacementsBuilder() {
++        }
++
++        @SuppressWarnings({"unchecked", "rawtypes"})
++        public <A extends ArgumentType<?>> void registerReplacement(
++            final Class<A> type,
++            final BiFunction<A, CommandBuildContext, ? extends ArgumentType<UpgradedArgument>> upgrader
++        ) {
++            this.replacements.put(type, (BiFunction) upgrader);
++        }
++    }
++
++    public record UpgradedArgument(String upgraded) {}
++
++    private static final class ItemParser_1_20_4 implements ArgumentType<UpgradedArgument> {
++        @Override
++        public UpgradedArgument parse(final StringReader reader) throws CommandSyntaxException {
++            final ResourceLocation id = ResourceLocation.read(reader);
++
++            final CompoundTag itemNBT = new CompoundTag();
++            itemNBT.putString("id", id.toString());
++            itemNBT.putInt("Count", 1);
++
++            if (reader.canRead() && reader.peek() == '{') {
++                itemNBT.put("tag", new TagParser(reader).readStruct());
++            }
++
++            final CompoundTag converted = MCDataConverter.convertTag(
++                MCTypeRegistry.ITEM_STACK, itemNBT, MCVersions.V1_20_4, SharedConstants.getCurrentVersion().getDataVersion().getVersion()
++            );
++
++            final String newId = converted.getString("id");
++
++            if (converted.contains("components", Tag.TAG_COMPOUND)) {
++                return new UpgradedArgument(newId + V3818_Commands.toCommandFormat(converted.getCompound("components")));
++            } else {
++                return new UpgradedArgument(newId);
++            }
++        }
++    }
++
++    private static final class ComponentParser_1_20_4 implements ArgumentType<UpgradedArgument> {
++        private static final Field JSON_READER_POS = Util.make(() -> {
++            try {
++                final Field field = JsonReader.class.getDeclaredField("pos");
++                field.setAccessible(true);
++                return field;
++            } catch (final NoSuchFieldException var1) {
++                throw new IllegalStateException("Couldn't get field 'pos' for JsonReader", var1);
++            }
++        });
++
++        private static final Field JSON_READER_LINESTART = Util.make(() -> {
++            try {
++                final Field field = JsonReader.class.getDeclaredField("lineStart");
++                field.setAccessible(true);
++                return field;
++            } catch (final NoSuchFieldException var1) {
++                throw new IllegalStateException("Couldn't get field 'lineStart' for JsonReader", var1);
++            }
++        });
++
++        @Override
++        public UpgradedArgument parse(final StringReader reader) throws CommandSyntaxException {
++            final JsonElement element;
++            try {
++                element = parseJson(reader);
++            } catch (final Exception e) {
++                throw new SimpleCommandExceptionType(new LiteralMessage(e.getMessage())).createWithContext(reader);
++            }
++            V3818_Commands.walkComponent(element);
++            return new UpgradedArgument(GsonHelper.toStableString(element));
++        }
++
++        public static JsonElement parseJson(final StringReader stringReader) {
++            final JsonReader jsonReader = new JsonReader(new java.io.StringReader(stringReader.getRemaining()));
++            jsonReader.setLenient(false);
++
++            final JsonElement jsonElement;
++            try {
++                jsonElement = Streams.parse(jsonReader);
++            } catch (final StackOverflowError var9) {
++                throw new JsonParseException(var9);
++            } finally {
++                stringReader.setCursor(stringReader.getCursor() + getPos(jsonReader));
++            }
++            return jsonElement;
++        }
++
++        private static int getPos(final JsonReader jsonReader) {
++            try {
++                return JSON_READER_POS.getInt(jsonReader) - JSON_READER_LINESTART.getInt(jsonReader);
++            } catch (IllegalAccessException var2) {
++                throw new IllegalStateException("Couldn't read position of JsonReader", var2);
++            }
++        }
++    }
++
++    // important: leadingSlash should not just be the result of a startsWith on command,
++    // it should reflect whether the command use is in a place that will skip a leading slash when parsing
++    public String upgradeCommandArguments(final String command, final boolean leadingSlash) {
++        final StringReader reader = new StringReader(command);
++        if (leadingSlash && reader.peek() == '/') {
++            reader.skip();
++        }
++        final ParseResults<CommandSourceStack> parseResult = this.dispatcher.parse(reader, this.source);
++        if (!parseResult.getExceptions().isEmpty()) {
++            return command;
++        }
++        final Map<StringRange, String> replacements = new LinkedHashMap<>();
++        final List<Pair<String, ParsedArgument<CommandSourceStack, ?>>> mergedArguments = new ArrayList<>();
++        addArguments(mergedArguments, parseResult.getContext());
++        mergedArguments.forEach(pair -> {
++            if (pair.value().getResult() instanceof UpgradedArgument upgraded) {
++                replacements.put(pair.value().getRange(), upgraded.upgraded());
++            }
++        });
++        String upgradedCommand = command;
++        while (!replacements.isEmpty()) {
++            final Map.Entry<StringRange, String> next = replacements.entrySet().iterator().next();
++            replacements.remove(next.getKey());
++            upgradedCommand = upgradedCommand.substring(0, next.getKey().getStart()) + next.getValue() + upgradedCommand.substring(next.getKey().getEnd());
++            // Update the offsets for the remaining replacements
++            final int diff = next.getValue().length() - next.getKey().getLength();
++            final Map<StringRange, String> replacementsCopy = new LinkedHashMap<>(replacements);
++            replacements.clear();
++            replacementsCopy.forEach((range, value) -> {
++                replacements.put(new StringRange(range.getStart() + diff, range.getEnd() + diff), value);
++            });
++        }
++        return upgradedCommand;
++    }
++
++    public String upgradeSingleArgument(
++        final Function<CommandBuildContext, ? extends ArgumentType<?>> argumentFactory,
++        final String input
++    ) {
++        final ArgumentType<?> argument = argumentFactory.apply(this.context);
++        final ArgumentType<?> replaced = this.replaceArgumentType(this.context, argument);
++        if (argument == replaced) {
++            return input;
++        }
++        try {
++            final UpgradedArgument parsed = (UpgradedArgument) replaced.parse(new StringReader(input));
++            return parsed.upgraded();
++        } catch (final CommandSyntaxException e) {
++            return input;
++        }
++    }
++
++    private static void addArguments(
++        final List<Pair<String, ParsedArgument<CommandSourceStack, ?>>> mergedArguments,
++        final @Nullable CommandContextBuilder<CommandSourceStack> context
++    ) {
++        if (context == null) {
++            return;
++        }
++        context.getArguments().forEach((name, argument) -> mergedArguments.add(Pair.of(name, argument)));
++        addArguments(mergedArguments, context.getChild());
++    }
++
++    private ArgumentType<?> replaceArgumentType(final CommandBuildContext ctx, final ArgumentType<?> type) {
++        final BiFunction<ArgumentType<?>, CommandBuildContext, ArgumentType<?>> upgrader =
++            this.replacements.get(type.getClass());
++        if (upgrader != null) {
++            return upgrader.apply(type, ctx);
++        }
++        return type;
++    }
++
++    record CopyResult(
++        CommandNode<CommandSourceStack> root,
++        boolean replaced,
++        List<CommandNode<CommandSourceStack>> aliases
++    ) {
++        CopyResult replacedResult() {
++            if (this.replaced) {
++                return this;
++            }
++            return new CopyResult(this.root, true, new ArrayList<>(this.aliases));
++        }
++    }
++
++    private CopyResult copyCommand(
++        final CommandNode<CommandSourceStack> parent,
++        final CommandNode<CommandSourceStack> node,
++        @Nullable CopyResult result
++    ) {
++        final CommandNode<CommandSourceStack> copy;
++        final boolean replaced;
++        if (node instanceof LiteralCommandNode<?> literal) {
++            if (node.getName().equals("execute") || node.getName().equals("return")) {
++                return new CopyResult(parent, false, new ArrayList<>());
++            }
++            if (node.getRedirect() != null) {
++                if (result != null) {
++                    throw new IllegalStateException("Cannot handle non-root redirects");
++                }
++                final List<CommandNode<CommandSourceStack>> aliases = new ArrayList<>();
++                aliases.add(node);
++                return new CopyResult(parent, false, aliases);
++            }
++            copy = new LiteralCommandNode<>(
++                literal.getLiteral(),
++                node.getCommand(),
++                node.getRequirement(),
++                null,
++                node.getRedirectModifier(),
++                node.isFork()
++            );
++            replaced = false;
++        } else if (node instanceof ArgumentCommandNode<?, ?>) {
++            final ArgumentCommandNode<CommandSourceStack, ?> argument =
++                (ArgumentCommandNode<CommandSourceStack, ?>) node;
++            final ArgumentType<?> replacedType = this.replaceArgumentType(this.context, argument.getType());
++            replaced = replacedType != argument.getType();
++            copy = new ArgumentCommandNode<>(
++                node.getName(),
++                replacedType,
++                node.getCommand(),
++                node.getRequirement(),
++                null,
++                node.getRedirectModifier(),
++                node.isFork(),
++                argument.getCustomSuggestions()
++            );
++        } else {
++            throw new UnsupportedOperationException();
++        }
++        if (result == null) {
++            result = new CopyResult(copy, false, new ArrayList<>());
++        }
++        if (replaced) {
++            result = result.replacedResult();
++        }
++        for (final CommandNode<CommandSourceStack> child : node.getChildren()) {
++            result = this.copyCommand(copy, child, result);
++        }
++        if (parent != this.dispatcher.getRoot()) {
++            parent.addChild(copy);
++        }
++        return result;
++    }
++
++    private static CommandBuildContext makeDummyCommandBuildContext() {
++        return Commands.createValidationContext(
++            new HolderLookup.Provider() {
++
++                @Override
++                public Stream<ResourceKey<? extends Registry<?>>> listRegistries() {
++                    return Stream.of();
++                }
++
++                @Override
++                public <T> Optional<HolderLookup.RegistryLookup<T>> lookup(
++                    final ResourceKey<? extends Registry<? extends T>> registryRef
++                ) {
++                    return Optional.of(new HolderLookup.RegistryLookup<T>() {
++                        @Override
++                        public ResourceKey<? extends Registry<? extends T>> key() {
++                            return registryRef;
++                        }
++
++                        @Override
++                        public Lifecycle registryLifecycle() {
++                            return Lifecycle.stable();
++                        }
++
++                        @Override
++                        public Stream<Holder.Reference<T>> listElements() {
++                            return Stream.of();
++                        }
++
++                        @Override
++                        public Stream<HolderSet.Named<T>> listTags() {
++                            return Stream.of();
++                        }
++
++                        @Override
++                        public Optional<Holder.Reference<T>> get(final ResourceKey<T> key) {
++                            return Optional.of(Holder.Reference.createStandAlone(this, key));
++                        }
++
++                        @Override
++                        public Optional<HolderSet.Named<T>> get(final TagKey<T> tag) {
++                            return Optional.of(HolderSet.emptyNamed(this, tag));
++                        }
++                    });
++                }
++            }
++        );
++    }
++}
 diff --git a/src/main/java/ca/spottedleaf/dataconverter/util/Int2IntArraySortedMap.java b/src/main/java/ca/spottedleaf/dataconverter/util/Int2IntArraySortedMap.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
@@ -24797,7 +28754,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 --- a/src/main/java/net/minecraft/data/structures/StructureUpdater.java
 +++ b/src/main/java/net/minecraft/data/structures/StructureUpdater.java
 @@ -0,0 +0,0 @@ public class StructureUpdater implements SnbtToNbt.Filter {
-             LOGGER.warn("SNBT Too old, do not forget to update: {} < {}: {}", i, 3678, name);
+             LOGGER.warn("SNBT Too old, do not forget to update: {} < {}: {}", i, 3798, name);
          }
  
 -        CompoundTag compoundTag = DataFixTypes.STRUCTURE.updateToCurrentVersion(DataFixers.getDataFixer(), nbt, i);
@@ -24810,65 +28767,70 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 --- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
 +++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
 @@ -0,0 +0,0 @@ public class ChunkStorage implements AutoCloseable {
-         int i = ChunkStorage.getVersion(nbttagcompound);
  
-         // CraftBukkit start
--        if (i < 1466) {
-+        if (false && i < 1466) { // Paper - no longer needed, data converter system handles it now
-             CompoundTag level = nbttagcompound.getCompound("Level");
-             if (level.getBoolean("TerrainPopulated") && !level.getBoolean("LightPopulated")) {
-                 ServerChunkCache cps = (generatoraccess == null) ? null : ((ServerLevel) generatoraccess).getChunkSource();
+         try {
+             // CraftBukkit start
+-            if (i < 1466) {
++            if (false && i < 1466) { // Paper - no longer needed, data converter system / DFU handles it now
+                 CompoundTag level = nbttagcompound.getCompound("Level");
+                 if (level.getBoolean("TerrainPopulated") && !level.getBoolean("LightPopulated")) {
+                     ServerChunkCache cps = (generatoraccess == null) ? null : ((ServerLevel) generatoraccess).getChunkSource();
 @@ -0,0 +0,0 @@ public class ChunkStorage implements AutoCloseable {
-         // CraftBukkit end
+             // CraftBukkit end
  
-         if (i < 1493) {
--            nbttagcompound = DataFixTypes.CHUNK.update(this.fixerUpper, nbttagcompound, i, 1493);
-+            ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.CHUNK, nbttagcompound, i, 1493); // Paper - replace chunk converter
-             if (nbttagcompound.getCompound("Level").getBoolean("hasLegacyStructureData")) {
-                 LegacyStructureDataHandler persistentstructurelegacy = this.getLegacyStructureHandler(resourcekey, supplier);
+             if (i < 1493) {
+-                nbttagcompound = DataFixTypes.CHUNK.update(this.fixerUpper, nbttagcompound, i, 1493);
++                ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.CHUNK, nbttagcompound, i, 1493); // Paper - replace chunk converter
+                 if (nbttagcompound.getCompound("Level").getBoolean("hasLegacyStructureData")) {
+                     LegacyStructureDataHandler persistentstructurelegacy = this.getLegacyStructureHandler(resourcekey, supplier);
  
 @@ -0,0 +0,0 @@ public class ChunkStorage implements AutoCloseable {
-         // Spigot end
+             // Spigot end
  
-         ChunkStorage.injectDatafixingContext(nbttagcompound, resourcekey, optional);
--        nbttagcompound = DataFixTypes.CHUNK.updateToCurrentVersion(this.fixerUpper, nbttagcompound, Math.max(1493, i));
-+        nbttagcompound = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.CHUNK, nbttagcompound, Math.max(1493, i), SharedConstants.getCurrentVersion().getDataVersion().getVersion()); // Paper - replace chunk converter
-         if (i < SharedConstants.getCurrentVersion().getDataVersion().getVersion()) {
-             NbtUtils.addCurrentDataVersion(nbttagcompound);
-         }
-diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java
+             ChunkStorage.injectDatafixingContext(nbttagcompound, resourcekey, optional);
+-            nbttagcompound = DataFixTypes.CHUNK.updateToCurrentVersion(this.fixerUpper, nbttagcompound, Math.max(1493, i));
++            nbttagcompound = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.CHUNK, nbttagcompound, Math.max(1493, i), SharedConstants.getCurrentVersion().getDataVersion().getVersion()); // Paper - replace chunk converter
+             if (i < SharedConstants.getCurrentVersion().getDataVersion().getVersion()) {
+                 NbtUtils.addCurrentDataVersion(nbttagcompound);
+             }
+diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java
-@@ -0,0 +0,0 @@ public class EntityStorage implements EntityPersistentStorage<Entity> {
- 
-     private CompoundTag upgradeChunkTag(CompoundTag chunkNbt) {
-         int i = NbtUtils.getDataVersion(chunkNbt, -1);
--        return DataFixTypes.ENTITY_CHUNK.updateToCurrentVersion(this.fixerUpper, chunkNbt, i);
-+        return ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.ENTITY_CHUNK, chunkNbt, i, net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion()); // Paper - route to new converter system
+--- a/src/main/java/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java
++++ b/src/main/java/net/minecraft/world/level/chunk/storage/SimpleRegionStorage.java
+@@ -0,0 +0,0 @@ public class SimpleRegionStorage implements AutoCloseable {
+         return this.worker.store(pos, nbt);
      }
  
-     @Override
-diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java
-@@ -0,0 +0,0 @@ public class SectionStorage<R> implements AutoCloseable {
-             int j = getVersion(dynamic);
-             int k = SharedConstants.getCurrentVersion().getDataVersion().getVersion();
-             boolean bl = j != k;
--            Dynamic<T> dynamic2 = this.type.update(this.fixerUpper, dynamic, j, k);
-+            // Paper start - route to new converter system
-+            Dynamic<T> dynamic2;
-+            if (this.type == net.minecraft.util.datafix.DataFixTypes.POI_CHUNK) {
-+                dynamic2 = new Dynamic<>(dynamic.getOps(), (T)ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.POI_CHUNK, (CompoundTag)dynamic.getValue(), j, k));
-+            } else {
-+                dynamic2 = this.type.update(this.fixerUpper, dynamic, j, k);
-+            }
-+            // Paper end - route to new converter system
-             OptionalDynamic<T> optionalDynamic = dynamic2.get("Sections");
++    // Paper start - rewrite data conversion system
++    private ca.spottedleaf.dataconverter.minecraft.datatypes.MCDataType getDataConverterType() {
++        if (this.dataFixType == DataFixTypes.ENTITY_CHUNK) {
++            return ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.ENTITY_CHUNK;
++        } else if (this.dataFixType == DataFixTypes.POI_CHUNK) {
++            return ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.POI_CHUNK;
++        } else {
++            throw new UnsupportedOperationException("For " + this.dataFixType.name());
++        }
++    }
++    // Paper end - rewrite data conversion system
++
+     public CompoundTag upgradeChunkTag(CompoundTag nbt, int oldVersion) {
+-        int i = NbtUtils.getDataVersion(nbt, oldVersion);
+-        return this.dataFixType.updateToCurrentVersion(this.fixerUpper, nbt, i);
++        // Paper start - rewrite data conversion system
++        final int dataVer = NbtUtils.getDataVersion(nbt, oldVersion);
++        return ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(this.getDataConverterType(), nbt, dataVer, net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion());
++        // Paper end - rewrite data conversion system
+     }
  
-             for (int l = this.levelHeightAccessor.getMinSection(); l < this.levelHeightAccessor.getMaxSection(); l++) {
+     public Dynamic<Tag> upgradeChunkTag(Dynamic<Tag> nbt, int oldVersion) {
+-        return this.dataFixType.updateToCurrentVersion(this.fixerUpper, nbt, oldVersion);
++        // Paper start - rewrite data conversion system
++        final CompoundTag converted = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(this.getDataConverterType(), (CompoundTag)nbt.getValue(), oldVersion, net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion());
++        return new Dynamic<>(net.minecraft.nbt.NbtOps.INSTANCE, converted);
++        // Paper end - rewrite data conversion system
+     }
+ 
+     public CompletableFuture<Void> synchronize(boolean sync) {
 diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java b/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 --- a/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java
@@ -24906,58 +28868,55 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 -        int i = NbtUtils.getDataVersion(nbttagcompound1, -1);
 +        int i = NbtUtils.getDataVersion(nbttagcompound1, -1); final int version = i; // Paper - obfuscation helpers
          Dynamic<?> dynamic = DataFixTypes.LEVEL.updateToCurrentVersion(dataFixer, new Dynamic(NbtOps.INSTANCE, nbttagcompound1), i);
-         Dynamic<?> dynamic1 = dynamic.get("Player").orElseEmptyMap();
--        Dynamic<?> dynamic2 = DataFixTypes.PLAYER.updateToCurrentVersion(dataFixer, dynamic1, i);
-+        Dynamic<?> dynamic2 = LevelStorageSource.dank(dynamic1, version); // Paper
- 
-         dynamic = dynamic.set("Player", dynamic2);
-         Dynamic<?> dynamic3 = dynamic.get("WorldGenSettings").orElseEmptyMap();
-@@ -0,0 +0,0 @@ public class LevelStorageSource {
-         return dynamic;
-     }
- 
-+    // Paper start
-+    private static <T> Dynamic<T> dank(final Dynamic<T> input, final int version) {
-+        return new Dynamic<>(input.getOps(), (T) ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.PLAYER, (CompoundTag)input.getValue(), version, net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion()));
-+    }
-+    // Paper end
-+
-     private LevelSummary readLevelSummary(LevelStorageSource.LevelDirectory save, boolean locked) {
-         Path path = save.dataFile();
  
++        // Paper start - replace data conversion system
+         dynamic = dynamic.update("Player", (dynamic1) -> {
+-            return DataFixTypes.PLAYER.updateToCurrentVersion(dataFixer, dynamic1, i);
++            return new Dynamic<>(
++                NbtOps.INSTANCE,
++                ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(
++                    ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.PLAYER,
++                    (net.minecraft.nbt.CompoundTag)dynamic1.getValue(),
++                    version, net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion()
++                )
++            );
+         });
++        // Paper end - replace data conversion system
+         dynamic = dynamic.update("WorldGenSettings", (dynamic1) -> {
+             return DataFixTypes.WORLD_GEN_SETTINGS.updateToCurrentVersion(dataFixer, dynamic1, i);
+         });
 diff --git a/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java b/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 --- a/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java
 +++ b/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java
 @@ -0,0 +0,0 @@ public class PlayerDataStorage {
-             // CraftBukkit end
+         }).map((nbttagcompound) -> {
              int i = NbtUtils.getDataVersion(nbttagcompound, -1);
  
 -            nbttagcompound = DataFixTypes.PLAYER.updateToCurrentVersion(this.fixerUpper, nbttagcompound, i);
-+            nbttagcompound = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.PLAYER, nbttagcompound, i, net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion()); // Paper - replace player converter
-             player.load(nbttagcompound);
-         }
- 
++            nbttagcompound = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.PLAYER, nbttagcompound, i, net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion()); // Paper - rewrite data conversion system
+             // entityhuman.load(nbttagcompound); // CraftBukkit - handled above
+             return nbttagcompound;
+         });
 diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 --- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
 +++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
 @@ -0,0 +0,0 @@ public final class CraftMagicNumbers implements UnsafeValues {
  
-         CompoundTag compound = deserializeNbtFromBytes(data);
+         net.minecraft.nbt.CompoundTag compound = deserializeNbtFromBytes(data);
          final int dataVersion = compound.getInt("DataVersion");
--        compound = (CompoundTag) MinecraftServer.getServer().fixerUpper.update(References.ITEM_STACK, new Dynamic<>(NbtOps.INSTANCE, compound), dataVersion, this.getDataVersion()).getValue();
--        return CraftItemStack.asCraftMirror(net.minecraft.world.item.ItemStack.of(compound));
-+        return CraftItemStack.asCraftMirror(net.minecraft.world.item.ItemStack.of(ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.ITEM_STACK, compound, dataVersion, this.getDataVersion()))); // Paper - rewrite dataconverter
+-        compound = (net.minecraft.nbt.CompoundTag) MinecraftServer.getServer().fixerUpper.update(References.ITEM_STACK, new Dynamic<>(NbtOps.INSTANCE, compound), dataVersion, this.getDataVersion()).getValue();
++        compound = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.ITEM_STACK, compound, dataVersion, this.getDataVersion()); // Paper - replace data conversion system
+         return CraftItemStack.asCraftMirror(net.minecraft.world.item.ItemStack.parse(MinecraftServer.getServer().registryAccess(), compound).orElseThrow());
      }
  
-     @Override
 @@ -0,0 +0,0 @@ public final class CraftMagicNumbers implements UnsafeValues {
  
-         CompoundTag compound = deserializeNbtFromBytes(data);
+         net.minecraft.nbt.CompoundTag compound = deserializeNbtFromBytes(data);
          int dataVersion = compound.getInt("DataVersion");
--        compound = (CompoundTag) MinecraftServer.getServer().fixerUpper.update(References.ENTITY, new Dynamic<>(NbtOps.INSTANCE, compound), dataVersion, this.getDataVersion()).getValue();
-+        compound = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.ENTITY, compound, dataVersion, getDataVersion()); // Paper - rewrite dataconverter
+-        compound = (net.minecraft.nbt.CompoundTag) MinecraftServer.getServer().fixerUpper.update(References.ENTITY, new Dynamic<>(NbtOps.INSTANCE, compound), dataVersion, this.getDataVersion()).getValue();
++        compound = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.ENTITY, compound, dataVersion, this.getDataVersion());
          if (!preserveUUID) {
              // Generate a new UUID so we don't have to worry about deserializing the same entity twice
              compound.remove("UUID");