Run versioned transformers on player data read and write

This commit is contained in:
Ruling-0 committed 2026-08-27 19:51:20 -05:00
1 parent 2e0b571550
commit 4f8e73f915
5 files changed
+138

No files matched your search

@@ -1,8 +1,55 @@
package com.gtnewhorizons.postea.api;
import java.util.function.Consumer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.nbt.NBTTagCompound;
import com.gtnewhorizons.postea.utility.NbtItemStacks;
import com.gtnewhorizons.postea.utility.VersionStamps;
/**
* A player's saved data as handed to {@link IVersionedTransformer#transformPlayer}: the root tag about to be
* loaded into the player, which holds the inventory, ender chest, {@code ForgeData}, and every extended-properties
* compound. The player object is constructed but not yet loaded from the tag.
*/
public final class PlayerDataTransformContext {
public static final int UNSTAMPED = VersionStamps.UNSTAMPED;
private final EntityPlayerMP player;
private final NBTTagCompound playerTag;
private final int storedVersion;
private final int currentVersion;
public PlayerDataTransformContext(EntityPlayerMP player, NBTTagCompound playerTag, int storedVersion,
int currentVersion) {
this.player = player;
this.playerTag = playerTag;
this.storedVersion = storedVersion;
this.currentVersion = currentVersion;
}
public EntityPlayerMP player() {
return player;
}
public NBTTagCompound playerTag() {
return playerTag;
}
public int storedVersion() {
return storedVersion;
}
public int currentVersion() {
return currentVersion;
}
/**
* Visits every serialized item stack anywhere in the player's data; see {@link NbtItemStacks#forEach}.
*/
public void forEachItemStackTag(Consumer<NBTTagCompound> visitor) {
NbtItemStacks.forEach(playerTag, visitor);
}
}
@@ -45,6 +45,7 @@ public final class PosteaLoadingPlugin implements IEarlyMixinLoader, IFMLLoading
final List<String> mixins = new ArrayList<>();
mixins.add("MixinItemStack");
mixins.add("MixinChunk");
mixins.add("MixinEntity");
mixins.add("MixinAnvilChunkLoader");
mixins.add("MixinNBTTagList");
return mixins;
@@ -0,0 +1,34 @@
package com.gtnewhorizons.postea.mixins.early;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.nbt.NBTTagCompound;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import com.gtnewhorizons.postea.utility.PlayerDataFixerUtility;
@Mixin(Entity.class)
public abstract class MixinEntity {
@Unique
private NBTTagCompound postea$versionStamps;
@Inject(method = "readFromNBT", at = @At("HEAD"), require = 1)
private void postea$transformPlayerData(NBTTagCompound tag, CallbackInfo ci) {
if ((Object) this instanceof EntityPlayerMP player) {
postea$versionStamps = PlayerDataFixerUtility.onPlayerRead(player, tag);
}
}
@Inject(method = "writeToNBT", at = @At("RETURN"), require = 1)
private void postea$stampPlayerData(NBTTagCompound tag, CallbackInfo ci) {
if ((Object) this instanceof EntityPlayerMP) {
PlayerDataFixerUtility.onPlayerWrite(tag, postea$versionStamps);
}
}
}
@@ -0,0 +1,55 @@
package com.gtnewhorizons.postea.utility;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.ReportedException;
import com.gtnewhorizons.postea.Postea;
import com.gtnewhorizons.postea.api.IVersionedTransformer;
import com.gtnewhorizons.postea.api.PlayerDataTransformContext;
import com.gtnewhorizons.postea.api.VersionedReplacementManager;
/**
* Runs versioned transformers over a server player's NBT as it is read, and stamps it as it is written. Hooked on
* {@code Entity.readFromNBT}/{@code writeToNBT} so both the {@code playerdata/<uuid>.dat} files and the
* single-player host's copy inside {@code level.dat} are covered.
*/
public final class PlayerDataFixerUtility {
private PlayerDataFixerUtility() {}
/**
* Transforms {@code tag} in place and returns a copy of the stamps it carried (null when unstamped).
*/
public static NBTTagCompound onPlayerRead(EntityPlayerMP player, NBTTagCompound tag) {
NBTTagCompound stamps = VersionStamps.read(tag);
for (IVersionedTransformer transformer : VersionedReplacementManager.transformers()) {
int current = transformer.currentVersion();
int stored = VersionStamps.stored(stamps, transformer.key());
if (stored == current) continue;
Postea.LOG.debug(
"{}: player {} from version {} to {}",
transformer.key(),
player.getCommandSenderName(),
stored,
current);
try {
transformer.transformPlayer(new PlayerDataTransformContext(player, tag, stored, current));
} catch (Throwable t) {
throw new ReportedException(
ChunkFixerUtility.describe(
t,
transformer,
stored,
current,
"Player",
player.getCommandSenderName() + " (" + player.getUniqueID() + ")"));
}
}
return stamps == null ? null : (NBTTagCompound) stamps.copy();
}
public static void onPlayerWrite(NBTTagCompound tag, NBTTagCompound carried) {
VersionStamps.write(tag, carried, VersionedReplacementManager.transformers());
}
}
@@ -8,6 +8,7 @@
"mixins": [
"MixinAnvilChunkLoader",
"MixinChunk",
"MixinEntity",
"MixinItemStack",
"MixinNBTTagList"
],