From 6f8a6e868a7e1df0b3863152fa5d890a8db5075b Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 16 Feb 2024 22:41:01 -0500 Subject: [PATCH] Improve compatibility with custom NBT (#183) --- .../openinv/internal/v1_19_R3/OpenPlayer.java | 83 ++++++++++++------ .../openinv/internal/v1_20_R2/OpenPlayer.java | 84 ++++++++++++------ .../openinv/internal/v1_20_R3/OpenPlayer.java | 85 +++++++++++++------ 3 files changed, 177 insertions(+), 75 deletions(-) diff --git a/internal/v1_19_R3/src/main/java/com/lishid/openinv/internal/v1_19_R3/OpenPlayer.java b/internal/v1_19_R3/src/main/java/com/lishid/openinv/internal/v1_19_R3/OpenPlayer.java index 347d9cb9..34c0891f 100644 --- a/internal/v1_19_R3/src/main/java/com/lishid/openinv/internal/v1_19_R3/OpenPlayer.java +++ b/internal/v1_19_R3/src/main/java/com/lishid/openinv/internal/v1_19_R3/OpenPlayer.java @@ -19,7 +19,6 @@ import net.minecraft.Util; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtIo; -import net.minecraft.nbt.NbtUtils; import net.minecraft.nbt.NumericTag; import net.minecraft.nbt.Tag; import net.minecraft.server.level.ServerPlayer; @@ -27,13 +26,48 @@ import org.apache.logging.log4j.LogManager; import org.bukkit.craftbukkit.v1_19_R3.CraftServer; import org.bukkit.craftbukkit.v1_19_R3.entity.CraftPlayer; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; +import java.util.Set; public class OpenPlayer extends CraftPlayer { + private static final Set RESET_TAGS = Set.of( + // net.minecraft.world.Entity#saveWithoutId(CompoundTag) + "CustomName", + "CustomNameVisible", + "Silent", + "NoGravity", + "Glowing", + "TicksFrozen", + "HasVisualFire", + "Tags", + "Passengers", + // net.minecraft.server.level.ServerPlayer#addAdditionalSaveData(CompoundTag) + // Intentional omissions to prevent mount loss: Attach, Entity, and RootVehicle + "warden_spawn_tracker", + "enteredNetherPosition", + "SpawnX", + "SpawnY", + "SpawnZ", + "SpawnForced", + "SpawnAngle", + "SpawnDimension", + // net.minecraft.world.entity.player.Player#addAdditionalSaveData(CompoundTag) + "ShoulderEntityLeft", + "ShoulderEntityRight", + "LastDeathLocation", + // net.minecraft.world.entity.LivingEntity#addAdditionalSaveData(CompoundTag) + "ActiveEffects", + "SleepingX", + "SleepingY", + "SleepingZ", + "Brain" + ); + public OpenPlayer(CraftServer server, ServerPlayer entity) { super(server, entity); } @@ -56,12 +90,13 @@ public void saveData() { try { PlayerDataStorage worldNBTStorage = player.server.getPlayerList().playerIo; - CompoundTag playerData = player.saveWithoutId(new CompoundTag()); + CompoundTag oldData = isOnline() ? null : worldNBTStorage.load(player); + CompoundTag playerData = getWritableTag(oldData); + playerData = player.saveWithoutId(playerData); setExtraData(playerData); - if (!isOnline()) { - // Preserve certain data when offline. - CompoundTag oldData = worldNBTStorage.load(player); + if (oldData != null) { + // Revert certain special data values when offline. revertSpecialValues(playerData, oldData); } @@ -75,27 +110,22 @@ public void saveData() { } } - private void revertSpecialValues(@NotNull CompoundTag newData, @Nullable CompoundTag oldData) { + @Contract("null -> new") + private @NotNull CompoundTag getWritableTag(@Nullable CompoundTag oldData) { if (oldData == null) { - return; + return new CompoundTag(); } - // Prevent vehicle deletion. - if (oldData.contains("RootVehicle", Tag.TAG_COMPOUND)) { - // See net.minecraft.server.players.PlayerList#save(ServerPlayer) - // See net.minecraft.server.level.ServerPlayer#addAdditionalSaveData(CompoundTag) - try { - Tag attach = oldData.get("Attach"); - if (attach != null) { - newData.putUUID("Attach", NbtUtils.loadUUID(attach)); - } - } catch (IllegalArgumentException ignored) { - // Likely will not re-mount successfully, but at least the mount will not be deleted. - } - newData.put("Entity", oldData.getCompound("Entity")); - newData.put("RootVehicle", oldData.getCompound("RootVehicle")); - } + // Copy old data. This is a deep clone, so operating on it should be safe. + oldData = oldData.copy(); + + // Remove vanilla/server data that is not written every time. + oldData.getAllKeys().removeIf(key -> RESET_TAGS.contains(key) || key.startsWith("Bukkit")); + return oldData; + } + + private void revertSpecialValues(@NotNull CompoundTag newData, @NotNull CompoundTag oldData) { // Revert automatic updates to play timestamps. copyValue(oldData, newData, "bukkit", "lastPlayed", NumericTag.class); copyValue(oldData, newData, "Paper", "LastSeen", NumericTag.class); @@ -111,8 +141,8 @@ private void copyValue( CompoundTag oldContainer = getTag(source, container, CompoundTag.class); CompoundTag newContainer = getTag(target, container, CompoundTag.class); - // Container being null means the server implementation doesn't store this data. - if (oldContainer == null || newContainer == null) { + // New container being null means the server implementation doesn't store this data. + if (newContainer == null) { return; } @@ -121,9 +151,12 @@ private void copyValue( } private @Nullable T getTag( - @NotNull CompoundTag container, + @Nullable CompoundTag container, @NotNull String key, @NotNull Class dataType) { + if (container == null) { + return null; + } Tag value = container.get(key); if (value == null || !dataType.isAssignableFrom(value.getClass())) { return null; diff --git a/internal/v1_20_R2/src/main/java/com/lishid/openinv/internal/v1_20_R2/OpenPlayer.java b/internal/v1_20_R2/src/main/java/com/lishid/openinv/internal/v1_20_R2/OpenPlayer.java index 4388d823..33677e0d 100644 --- a/internal/v1_20_R2/src/main/java/com/lishid/openinv/internal/v1_20_R2/OpenPlayer.java +++ b/internal/v1_20_R2/src/main/java/com/lishid/openinv/internal/v1_20_R2/OpenPlayer.java @@ -20,20 +20,55 @@ import net.minecraft.Util; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtIo; -import net.minecraft.nbt.NbtUtils; import net.minecraft.nbt.NumericTag; import net.minecraft.nbt.Tag; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.level.storage.PlayerDataStorage; import org.bukkit.craftbukkit.v1_20_R2.CraftServer; import org.bukkit.craftbukkit.v1_20_R2.entity.CraftPlayer; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; +import java.util.Set; public class OpenPlayer extends CraftPlayer { + private static final Set RESET_TAGS = Set.of( + // net.minecraft.world.Entity#saveWithoutId(CompoundTag) + "CustomName", + "CustomNameVisible", + "Silent", + "NoGravity", + "Glowing", + "TicksFrozen", + "HasVisualFire", + "Tags", + "Passengers", + // net.minecraft.server.level.ServerPlayer#addAdditionalSaveData(CompoundTag) + // Intentional omissions to prevent mount loss: Attach, Entity, and RootVehicle + "warden_spawn_tracker", + "enteredNetherPosition", + "SpawnX", + "SpawnY", + "SpawnZ", + "SpawnForced", + "SpawnAngle", + "SpawnDimension", + // net.minecraft.world.entity.player.Player#addAdditionalSaveData(CompoundTag) + "ShoulderEntityLeft", + "ShoulderEntityRight", + "LastDeathLocation", + // net.minecraft.world.entity.LivingEntity#addAdditionalSaveData(CompoundTag) + "ActiveEffects", // Backwards compat: Renamed from 1.19 + "active_effects", + "SleepingX", + "SleepingY", + "SleepingZ", + "Brain" + ); + public OpenPlayer(CraftServer server, ServerPlayer entity) { super(server, entity); } @@ -50,12 +85,13 @@ public void saveData() { try { PlayerDataStorage worldNBTStorage = player.server.getPlayerList().playerIo; - CompoundTag playerData = player.saveWithoutId(new CompoundTag()); + CompoundTag oldData = isOnline() ? null : worldNBTStorage.load(player); + CompoundTag playerData = getWritableTag(oldData); + playerData = player.saveWithoutId(playerData); setExtraData(playerData); - if (!isOnline()) { - // Preserve certain data when offline. - CompoundTag oldData = worldNBTStorage.load(player); + if (oldData != null) { + // Revert certain special data values when offline. revertSpecialValues(playerData, oldData); } @@ -69,27 +105,22 @@ public void saveData() { } } - private void revertSpecialValues(@NotNull CompoundTag newData, @Nullable CompoundTag oldData) { + @Contract("null -> new") + private @NotNull CompoundTag getWritableTag(@Nullable CompoundTag oldData) { if (oldData == null) { - return; + return new CompoundTag(); } - // Prevent vehicle deletion. - if (oldData.contains("RootVehicle", Tag.TAG_COMPOUND)) { - // See net.minecraft.server.players.PlayerList#save(ServerPlayer) - // See net.minecraft.server.level.ServerPlayer#addAdditionalSaveData(CompoundTag) - try { - Tag attach = oldData.get("Attach"); - if (attach != null) { - newData.putUUID("Attach", NbtUtils.loadUUID(attach)); - } - } catch (IllegalArgumentException ignored) { - // Likely will not re-mount successfully, but at least the mount will not be deleted. - } - newData.put("Entity", oldData.getCompound("Entity")); - newData.put("RootVehicle", oldData.getCompound("RootVehicle")); - } + // Copy old data. This is a deep clone, so operating on it should be safe. + oldData = oldData.copy(); + + // Remove vanilla/server data that is not written every time. + oldData.getAllKeys().removeIf(key -> RESET_TAGS.contains(key) || key.startsWith("Bukkit")); + return oldData; + } + + private void revertSpecialValues(@NotNull CompoundTag newData, @NotNull CompoundTag oldData) { // Revert automatic updates to play timestamps. copyValue(oldData, newData, "bukkit", "lastPlayed", NumericTag.class); copyValue(oldData, newData, "Paper", "LastSeen", NumericTag.class); @@ -105,8 +136,8 @@ private void copyValue( CompoundTag oldContainer = getTag(source, container, CompoundTag.class); CompoundTag newContainer = getTag(target, container, CompoundTag.class); - // Container being null means the server implementation doesn't store this data. - if (oldContainer == null || newContainer == null) { + // New container being null means the server implementation doesn't store this data. + if (newContainer == null) { return; } @@ -115,9 +146,12 @@ private void copyValue( } private @Nullable T getTag( - @NotNull CompoundTag container, + @Nullable CompoundTag container, @NotNull String key, @NotNull Class dataType) { + if (container == null) { + return null; + } Tag value = container.get(key); if (value == null || !dataType.isAssignableFrom(value.getClass())) { return null; diff --git a/internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/OpenPlayer.java b/internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/OpenPlayer.java index 23ed6bb9..08017f9f 100644 --- a/internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/OpenPlayer.java +++ b/internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/OpenPlayer.java @@ -20,21 +20,56 @@ import net.minecraft.Util; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtIo; -import net.minecraft.nbt.NbtUtils; import net.minecraft.nbt.NumericTag; import net.minecraft.nbt.Tag; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.level.storage.PlayerDataStorage; import org.bukkit.craftbukkit.v1_20_R3.CraftServer; import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Set; public class OpenPlayer extends CraftPlayer { + private static final Set RESET_TAGS = Set.of( + // net.minecraft.world.Entity#saveWithoutId(CompoundTag) + "CustomName", + "CustomNameVisible", + "Silent", + "NoGravity", + "Glowing", + "TicksFrozen", + "HasVisualFire", + "Tags", + "Passengers", + // net.minecraft.server.level.ServerPlayer#addAdditionalSaveData(CompoundTag) + // Intentional omissions to prevent mount loss: Attach, Entity, and RootVehicle + "warden_spawn_tracker", + "enteredNetherPosition", + "SpawnX", + "SpawnY", + "SpawnZ", + "SpawnForced", + "SpawnAngle", + "SpawnDimension", + // net.minecraft.world.entity.player.Player#addAdditionalSaveData(CompoundTag) + "ShoulderEntityLeft", + "ShoulderEntityRight", + "LastDeathLocation", + // net.minecraft.world.entity.LivingEntity#addAdditionalSaveData(CompoundTag) + "ActiveEffects", // Backwards compat: Renamed from 1.19 + "active_effects", + "SleepingX", + "SleepingY", + "SleepingZ", + "Brain" + ); + public OpenPlayer(CraftServer server, ServerPlayer entity) { super(server, entity); } @@ -51,12 +86,13 @@ public void saveData() { try { PlayerDataStorage worldNBTStorage = player.server.getPlayerList().playerIo; - CompoundTag playerData = player.saveWithoutId(new CompoundTag()); + CompoundTag oldData = isOnline() ? null : worldNBTStorage.load(player); + CompoundTag playerData = getWritableTag(oldData); + playerData = player.saveWithoutId(playerData); setExtraData(playerData); - if (!isOnline()) { - // Preserve certain data when offline. - CompoundTag oldData = worldNBTStorage.load(player); + if (oldData != null) { + // Revert certain special data values when offline. revertSpecialValues(playerData, oldData); } @@ -71,27 +107,23 @@ public void saveData() { } } - private void revertSpecialValues(@NotNull CompoundTag newData, @Nullable CompoundTag oldData) { + @Contract("null -> new") + private @NotNull CompoundTag getWritableTag(@Nullable CompoundTag oldData) { if (oldData == null) { - return; + return new CompoundTag(); } - // Prevent vehicle deletion. - if (oldData.contains("RootVehicle", Tag.TAG_COMPOUND)) { - // See net.minecraft.server.players.PlayerList#save(ServerPlayer) - // See net.minecraft.server.level.ServerPlayer#addAdditionalSaveData(CompoundTag) - try { - Tag attach = oldData.get("Attach"); - if (attach != null) { - newData.putUUID("Attach", NbtUtils.loadUUID(attach)); - } - } catch (IllegalArgumentException ignored) { - // Likely will not re-mount successfully, but at least the mount will not be deleted. - } - newData.put("Entity", oldData.getCompound("Entity")); - newData.put("RootVehicle", oldData.getCompound("RootVehicle")); - } + // Copy old data. This is a deep clone, so operating on it should be safe. + oldData = oldData.copy(); + + // Remove vanilla/server data that is not written every time. + oldData.getAllKeys() + .removeIf(key -> RESET_TAGS.contains(key) || key.startsWith("Bukkit")); + return oldData; + } + + private void revertSpecialValues(@NotNull CompoundTag newData, @NotNull CompoundTag oldData) { // Revert automatic updates to play timestamps. copyValue(oldData, newData, "bukkit", "lastPlayed", NumericTag.class); copyValue(oldData, newData, "Paper", "LastSeen", NumericTag.class); @@ -107,8 +139,8 @@ private void copyValue( CompoundTag oldContainer = getTag(source, container, CompoundTag.class); CompoundTag newContainer = getTag(target, container, CompoundTag.class); - // Container being null means the server implementation doesn't store this data. - if (oldContainer == null || newContainer == null) { + // New container being null means the server implementation doesn't store this data. + if (newContainer == null) { return; } @@ -117,9 +149,12 @@ private void copyValue( } private @Nullable T getTag( - @NotNull CompoundTag container, + @Nullable CompoundTag container, @NotNull String key, @NotNull Class dataType) { + if (container == null) { + return null; + } Tag value = container.get(key); if (value == null || !dataType.isAssignableFrom(value.getClass())) { return null;