RKB00015 Myth of DataFix - Glease/RKB GitHub Wiki

IT SHOULD BE NITICED THAT THIS DOCUMENT IS OUT OF DATE AND NEED A GOOD CLEAN UP.

In mc 1.9 (or say snapshot 15wXXX) mojang introduced a new concept which the MCP guys(or whoever provide the name) call it "DataFix". The core of this concept is an interface IDataFixer and its sole implementor, DataFixer. Other classes in the net.minecraft.util.datafix and its sub-packages (not so correct in java, though) are also important.

Purpose of data fixes

Well, it's to fix the data, appearently. There are some changes in the saved nbt format, like UUID is now stored as two longs instead of as a string, which is more space efficient. Mojang wants to move on but not with lots of compatibility code, so here comes the data fixes.

Uses to the modders

Appearently if you want to port an old mod forward but with NBT data changed, you can utilize this mechanism. You could try retreive the data fixer from MinecraftServer and register necessary stuff? If it works, you should do it before any world is loaded. FMLServerAboutToStartEvent is probably a good start.

How to use it

While the core of the concept is IDataFixer, client code actually don't implement it. You will actually use IFixableData and IDataWalker.

IFixableData

IFixableData is a combination of two step: identify data to fix and do the actual fix. Generally they are about VERY SPECIFIC DATA FIXES, like sign data fix, UUID storage mechanism change, health change, etc. You will probably only use this. Please refer to real story below on how to use.

Vanilla DataVersion Table

Here is an incomplete list of vanilla data version mapping. Any support is on this is welcomed. (I guess the magic number is in fact a serial number of all public builds starting from one or zero, including snapshot)

MC version Magic number value
1.12 1139
1.11.2 922
1.10.2 512
1.9.4 184
1.9 169
1.8.x or older no, there is apperantly no DataVersion for that.

However you should use your own mod's version instead of

IDataWalker

IDataWalker is a walker desgined to walk a NBTTagCompound inside another NBTTagCompound. Forge doesn't come with a fluid walker (because fluid tank serialization isn't standardized), so if you need to fix some fluid stored in tanks and you know how these tanks are serializing your fluid, you can implement one IDataWalker.

To every modder whose custom fluid tank's serialized form differ from the standard net.minecraftforge.fluids.FluidTank's serialized form (and also happen to see this):

I hereby recommend you to write your IDataWalker for your fluid tank so there will be no need for someone else to write a IDataWalker for you which may be out of sync at any momment.

Real story

A common problem for many mods porting to 1.9+ is energy api change. No rf, but some other rather werid apis like tesla. All energy stuff needs refreshing its data to a new version. You may just say omg screw it let's get a complete rewrite and push ModAwesome 2 and say goodbye to old cumbersome ModAwesome 1 - may work but there will be an army of black, white, red and green to force you to pick up old content, or later make ModAwesome Legacy. You may also try to publish a convertor to convert all data. The worst case is to carry on and ignore those old data (that's exactly what happens in the 1.5.x -> 1.6.x migration).

No matter what you want, I will show you how to do it in the DataFixer way.

Suppose I have a 1.9 mod ModAwesome which has many machines that accept rf energy and produce some ... thing or fluid or - that's not our concern - and we are going to port it to 1.10.2. Old energy serialization code is like this

...
@Override
public void writeToNBT(NBTTagCompound tag) {
    super.writeToNBT(tag);
    tag.setInt("Capacity", capacity);
    tag.setInt("Stored", stored);
    tag.setTag("owner", ModAwesome.MODID);
}

As shown above, the serialized form is not complex. The new api, suppose it to called, "Electric Unit", or, uh, "EU" in short. You want there to be a one-to-one exchange rate.

The EU api forces you to follow some serializing standard (the reason is, I'm going to show you how to use data fix). It must has a tag compound with name "EUs". The compound should be a structure like this:

EUs
\- MaxCapacity: int
\- StoredEnergy: int

There is a lot of differences in two forms, but there could be a easy translate in between - create a new tag, fill it with old data, attach it, and remove old tags, then everything is done. So let's see how we should go.

First let's create an IFixableData. Because we are porting to 1.10.2, so its version should be 512. Then, we should go build the class. It's quite an easy task, which took me only 3 minutes.

public class RF2EUStorage implements IFixableData {
    @Override
    public int getFixVersion() {
        return 512; // fix data from pre-1.10.2
    }
    
    @Override
    public NBTTagCompound fixTagCompound(NBTTagCompound tag) {
        if (tag.getString("id").startsWith(ModAwesome.MODID + ":")) && tag.hasTag("Energy", NBTIDs.TAG_INT)) {
            NBTTagCompound e = new NBTTagCompound();
            e.setInteger("MaxCapacity", tag.getInteger("Capacity"));
            e.setInteger("StoredEnegey", tag.getInteger("Stored"));
            tag.removeTag("Capacity");
            tag.removeTag("Stored");
            tag.setTag("EUs", e);
        }
        
        return tag;
    }
}

Then register it properly, this has to be done in SidedProxy because it's stored in Minecraft/MinecraftServer

//Server proxy:
server.getDataFixer().registerFix(FixTypes.BLOCK_ENTITY, new RF2EUStorage()); 
//Client proxy:
Minecraft.getMinecraft().getDataFixer().registerFix(FixTypes.BLOCK_ENTITY, new RF2EUStorage());

Ok, that's it. You may also decide to use singleton but it won't make big differece.

NOTE: Since a certain forge version you can jsut use FMLCommonHandler.getDataFixer() instead of using a proxy.