Skip to content

Commit

Permalink
Improve compatibility with custom NBT (#183)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jikoo authored Feb 17, 2024
1 parent 70c1ecf commit 6f8a6e8
Show file tree
Hide file tree
Showing 3 changed files with 177 additions and 75 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,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.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<String> 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);
}
Expand All @@ -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);
}

Expand All @@ -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);
Expand All @@ -111,8 +141,8 @@ private <T extends Tag> 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;
}

Expand All @@ -121,9 +151,12 @@ private <T extends Tag> void copyValue(
}

private <T extends Tag> @Nullable T getTag(
@NotNull CompoundTag container,
@Nullable CompoundTag container,
@NotNull String key,
@NotNull Class<T> dataType) {
if (container == null) {
return null;
}
Tag value = container.get(key);
if (value == null || !dataType.isAssignableFrom(value.getClass())) {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> 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);
}
Expand All @@ -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);
}

Expand All @@ -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);
Expand All @@ -105,8 +136,8 @@ private <T extends Tag> 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;
}

Expand All @@ -115,9 +146,12 @@ private <T extends Tag> void copyValue(
}

private <T extends Tag> @Nullable T getTag(
@NotNull CompoundTag container,
@Nullable CompoundTag container,
@NotNull String key,
@NotNull Class<T> dataType) {
if (container == null) {
return null;
}
Tag value = container.get(key);
if (value == null || !dataType.isAssignableFrom(value.getClass())) {
return null;
Expand Down
Loading

0 comments on commit 6f8a6e8

Please sign in to comment.