From d7843319166f01785aafa6ae998a1bfb411d1fc1 Mon Sep 17 00:00:00 2001 From: MewTwoKing Date: Wed, 4 Jun 2025 18:21:01 -0500 Subject: [PATCH] Save block-NBT in structure module --- .../tc/oc/pgm/snapshot/WorldSnapshot.java | 32 ++++++++++- .../platform/modern/impl/ModernNMSHacks.java | 49 ++++++++++++---- .../platform/sportpaper/impl/SpNMSHacks.java | 56 ++++++++++++++----- .../java/tc/oc/pgm/util/nms/NMSHacks.java | 4 ++ 4 files changed, 116 insertions(+), 25 deletions(-) diff --git a/core/src/main/java/tc/oc/pgm/snapshot/WorldSnapshot.java b/core/src/main/java/tc/oc/pgm/snapshot/WorldSnapshot.java index 6d702ac2af..ddde18811c 100644 --- a/core/src/main/java/tc/oc/pgm/snapshot/WorldSnapshot.java +++ b/core/src/main/java/tc/oc/pgm/snapshot/WorldSnapshot.java @@ -2,6 +2,7 @@ import java.util.HashMap; import java.util.Map; +import org.bukkit.Bukkit; import org.bukkit.ChunkSnapshot; import org.bukkit.World; import org.bukkit.block.Block; @@ -14,10 +15,12 @@ import tc.oc.pgm.util.chunk.ChunkVector; import tc.oc.pgm.util.material.BlockMaterialData; import tc.oc.pgm.util.material.MaterialData; +import tc.oc.pgm.util.nms.NMSHacks; public class WorldSnapshot { private final World world; private final Map chunkSnapshots = new HashMap<>(); + private final Map savedNBT = new HashMap<>(); private final BudgetWorldEdit worldEdit; public WorldSnapshot(World world) { @@ -80,11 +83,36 @@ public void saveSnapshot(ChunkVector cv, @Nullable BlockState oldState) { } public void saveRegion(Region region) { - region.getChunkPositions().forEach(cv -> this.saveSnapshot(cv, null)); + Region.Static staticRegion = region.getStatic(world); + staticRegion.getChunkPositions().forEach(cv -> this.saveSnapshot(cv, null)); + staticRegion.getBlockVectors().forEach(pos -> { + Block block = world.getBlockAt(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ()); + try { + Object tag = NMSHacks.NMS_HACKS.getBlockNBT(block); + if (tag != null) savedNBT.put(pos, tag); + } catch (Throwable t) { + Bukkit.getLogger().info("Failed to save NBT for block at " + pos + ": " + t.getMessage()); + } + }); } public void placeBlocks(Region region, BlockVector offset, boolean update) { - worldEdit.placeBlocks(region, offset, update); + Region.Static staticRegion = region.getStatic(world); + worldEdit.placeBlocks(staticRegion, offset, update); + staticRegion.getBlockVectors().forEach(pos -> { + Block block = world.getBlockAt( + pos.getBlockX() + offset.getBlockX(), + pos.getBlockY() + offset.getBlockY(), + pos.getBlockZ() + offset.getBlockZ()); + Object tag = savedNBT.get(pos); + if (tag != null) { + try { + NMSHacks.NMS_HACKS.setBlockNBT(block, tag); + } catch (Throwable t) { + Bukkit.getLogger().info("Failed to restore NBT at " + pos + ": " + t.getMessage()); + } + } + }); } public void removeBlocks(Region region, BlockVector offset, boolean update) { diff --git a/platform/platform-modern/src/main/java/tc/oc/pgm/platform/modern/impl/ModernNMSHacks.java b/platform/platform-modern/src/main/java/tc/oc/pgm/platform/modern/impl/ModernNMSHacks.java index 9d4f0b1ba5..6b137927ee 100644 --- a/platform/platform-modern/src/main/java/tc/oc/pgm/platform/modern/impl/ModernNMSHacks.java +++ b/platform/platform-modern/src/main/java/tc/oc/pgm/platform/modern/impl/ModernNMSHacks.java @@ -15,8 +15,11 @@ import java.util.UUID; import java.util.logging.Level; import net.kyori.adventure.text.Component; +import net.minecraft.core.BlockPos; +import net.minecraft.core.HolderLookup; import net.minecraft.core.RegistryAccess; import net.minecraft.core.registries.Registries; +import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtException; import net.minecraft.nbt.ReportedNbtException; import net.minecraft.resources.ResourceKey; @@ -31,6 +34,8 @@ import net.minecraft.world.level.CustomSpawner; import net.minecraft.world.level.GameRules; import net.minecraft.world.level.biome.BiomeManager; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.LevelChunkSection; import net.minecraft.world.level.chunk.status.ChunkStatus; import net.minecraft.world.level.dimension.LevelStem; @@ -38,12 +43,7 @@ import net.minecraft.world.level.storage.LevelStorageSource; import net.minecraft.world.level.storage.PrimaryLevelData; import net.minecraft.world.level.validation.ContentValidationException; -import org.bukkit.Bukkit; -import org.bukkit.Chunk; -import org.bukkit.Material; -import org.bukkit.Nameable; -import org.bukkit.World; -import org.bukkit.WorldCreator; +import org.bukkit.*; import org.bukkit.block.Block; import org.bukkit.craftbukkit.CraftChunk; import org.bukkit.craftbukkit.CraftServer; @@ -52,10 +52,7 @@ import org.bukkit.craftbukkit.entity.CraftFirework; import org.bukkit.craftbukkit.generator.CraftWorldInfo; import org.bukkit.craftbukkit.util.CraftMagicNumbers; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Fireball; -import org.bukkit.entity.Firework; -import org.bukkit.entity.Player; +import org.bukkit.entity.*; import org.bukkit.event.player.PlayerPickupArrowEvent; import org.bukkit.event.player.PlayerPickupItemEvent; import org.bukkit.event.world.WorldLoadEvent; @@ -393,4 +390,36 @@ public int getMaxWorldSize(World world) { public int allocateEntityId() { return Bukkit.getUnsafe().nextEntityId(); } + + @Override + public Object getBlockNBT(Block block) { + ServerLevel level = ((CraftWorld) block.getWorld()).getHandle(); + BlockPos pos = new BlockPos(block.getX(), block.getY(), block.getZ()); + BlockEntity be = level.getBlockEntity(pos); + + if (be == null) return null; + + CompoundTag tag = be.saveWithFullMetadata(level.registryAccess()); + return tag; + } + + @Override + public void setBlockNBT(Block block, Object tag) { + if (!(tag instanceof CompoundTag nbt)) return; + + ServerLevel level = ((CraftWorld) block.getWorld()).getHandle(); + BlockPos pos = new BlockPos(block.getX(), block.getY(), block.getZ()); + BlockState state = level.getBlockState(pos); + + BlockEntity existing = level.getBlockEntity(pos); + if (existing != null) { + level.removeBlockEntity(pos); + } + + HolderLookup.Provider registryLookup = level.registryAccess(); + BlockEntity loaded = BlockEntity.loadStatic(pos, state, nbt, registryLookup); + if (loaded != null) { + level.setBlockEntity(loaded); + } + } } diff --git a/platform/platform-sportpaper/src/main/java/tc/oc/pgm/platform/sportpaper/impl/SpNMSHacks.java b/platform/platform-sportpaper/src/main/java/tc/oc/pgm/platform/sportpaper/impl/SpNMSHacks.java index a4fee8c33d..b6669f86d4 100644 --- a/platform/platform-sportpaper/src/main/java/tc/oc/pgm/platform/sportpaper/impl/SpNMSHacks.java +++ b/platform/platform-sportpaper/src/main/java/tc/oc/pgm/platform/sportpaper/impl/SpNMSHacks.java @@ -8,21 +8,11 @@ import java.util.ArrayList; import java.util.List; import java.util.UUID; -import net.minecraft.server.v1_8_R3.ChunkSection; -import net.minecraft.server.v1_8_R3.EntityArrow; -import net.minecraft.server.v1_8_R3.EntityFireball; -import net.minecraft.server.v1_8_R3.EntityFireworks; -import net.minecraft.server.v1_8_R3.IBlockData; -import net.minecraft.server.v1_8_R3.IDataManager; -import net.minecraft.server.v1_8_R3.NBTTagCompound; -import net.minecraft.server.v1_8_R3.ServerNBTManager; -import net.minecraft.server.v1_8_R3.WorldData; -import net.minecraft.server.v1_8_R3.WorldServer; -import org.bukkit.Bukkit; +import net.minecraft.server.v1_8_R3.*; +import org.bukkit.*; import org.bukkit.Chunk; import org.bukkit.Material; import org.bukkit.World; -import org.bukkit.WorldCreator; import org.bukkit.block.Block; import org.bukkit.craftbukkit.v1_8_R3.CraftChunk; import org.bukkit.craftbukkit.v1_8_R3.CraftWorld; @@ -98,7 +88,6 @@ public void resumeServer() { @Override public Inventory createFakeInventory(Player viewer, Inventory realInventory) { if (realInventory.hasCustomName()) { - //noinspection deprecation return realInventory instanceof DoubleChestInventory ? Bukkit.createInventory(viewer, realInventory.getSize(), realInventory.getName()) : Bukkit.createInventory(viewer, realInventory.getType(), realInventory.getName()); @@ -229,4 +218,45 @@ public int getMaxWorldSize(World world) { public int allocateEntityId() { return Bukkit.allocateEntityId(); } + + @Override + public Object getBlockNBT(Block block) { + TileEntity tile = ((CraftWorld) block.getWorld()) + .getHandle() + .getTileEntity(new BlockPosition(block.getX(), block.getY(), block.getZ())); + if (tile == null) return null; + + NBTTagCompound tag = new NBTTagCompound(); + tile.b(tag); // Save + return tag; + } + + @Override + public void setBlockNBT(Block block, Object tag) { + if (!(tag instanceof NBTTagCompound)) return; + + WorldServer world = ((CraftWorld) block.getWorld()).getHandle(); + BlockPosition pos = new BlockPosition(block.getX(), block.getY(), block.getZ()); + NBTTagCompound compound = (NBTTagCompound) tag; + + compound.setInt("x", pos.getX()); + compound.setInt("y", pos.getY()); + compound.setInt("z", pos.getZ()); + + TileEntity tile = world.getTileEntity(pos); + if (tile == null) { + // Force block update to trigger tile entity re-creation + IBlockData blockData = world.getType(pos); + world.setTypeAndData(pos, Blocks.AIR.getBlockData(), 0); // Clear block + world.setTypeAndData(pos, blockData, 3); // Restore block + + tile = world.getTileEntity(pos); + if (tile == null) { + return; + } + } + + tile.a(compound); // Load NBT + tile.update(); + } } diff --git a/util/src/main/java/tc/oc/pgm/util/nms/NMSHacks.java b/util/src/main/java/tc/oc/pgm/util/nms/NMSHacks.java index 8b45ef008e..8316da2e9a 100644 --- a/util/src/main/java/tc/oc/pgm/util/nms/NMSHacks.java +++ b/util/src/main/java/tc/oc/pgm/util/nms/NMSHacks.java @@ -57,4 +57,8 @@ public interface NMSHacks { int getMaxWorldSize(World world); int allocateEntityId(); + + Object getBlockNBT(Block block); + + void setBlockNBT(Block block, Object tag); }