From 83bb3966ca29d10af493d07d5590f076cafc4466 Mon Sep 17 00:00:00 2001 From: IronApollo Date: Sat, 1 Feb 2020 20:11:02 -0500 Subject: [PATCH] Update adapter to 1.15.2 Adapter updated to 1.15.2, mostly a clone of 1.15 / 1.15.1's adapter. I encountered no issues from this, but the field names for our reflections may need to be double-checked for accuracy in this Minecraft version. --- .gitignore | 1 + .../mc1_15_2/BlockMaterial_1_15_2.java | 149 +++++ .../mc1_15_2/BukkitAdapter_1_15_2.java | 255 ++++++++ .../mc1_15_2/BukkitGetBlocks_1_15_2.java | 618 ++++++++++++++++++ .../adapter/mc1_15_2/MapChunkUtil_1_15_2.java | 28 + .../mc1_15_2/nbt/LazyCompoundTag_1_15_2.java | 152 +++++ .../worldedit/bukkit/WorldEditPlugin.java | 2 + .../adapter/impl/FAWE_Spigot_v1_15_R2.java | 451 +++++++++++++ 8 files changed, 1656 insertions(+) create mode 100644 worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15_2/BlockMaterial_1_15_2.java create mode 100644 worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15_2/BukkitAdapter_1_15_2.java create mode 100644 worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15_2/BukkitGetBlocks_1_15_2.java create mode 100644 worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15_2/MapChunkUtil_1_15_2.java create mode 100644 worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15_2/nbt/LazyCompoundTag_1_15_2.java create mode 100644 worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_15_R2.java diff --git a/.gitignore b/.gitignore index abbab0e31..e5db6200d 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ eclipse *.iml *.ipr *.iws +/.metadata/ bin build diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15_2/BlockMaterial_1_15_2.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15_2/BlockMaterial_1_15_2.java new file mode 100644 index 000000000..cfa52eb1d --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15_2/BlockMaterial_1_15_2.java @@ -0,0 +1,149 @@ +package com.boydti.fawe.bukkit.adapter.mc1_15_2; + +import com.sk89q.util.ReflectionUtil; +import com.sk89q.worldedit.world.registry.BlockMaterial; +import net.minecraft.server.v1_15_R1.*; +import org.bukkit.craftbukkit.v1_15_R1.block.data.CraftBlockData; + +public class BlockMaterial_1_15_2 implements BlockMaterial { + private final Block block; + private final IBlockData defaultState; + private final Material material; + private final boolean isTranslucent; + private final CraftBlockData craftBlockData; + private final org.bukkit.Material craftMaterial; + + public BlockMaterial_1_15_2(Block block) { + this(block, block.getBlockData()); + } + + public BlockMaterial_1_15_2(Block block, IBlockData defaultState) { + this.block = block; + this.defaultState = defaultState; + this.material = defaultState.getMaterial(); + this.craftBlockData = CraftBlockData.fromData(defaultState); + this.craftMaterial = craftBlockData.getMaterial(); + this.isTranslucent = ReflectionUtil.getField(Block.class, block, "v"); + } + + public Block getBlock() { + return block; + } + + public IBlockData getState() { + return defaultState; + } + + public CraftBlockData getCraftBlockData() { + return craftBlockData; + } + + public Material getMaterial() { + return material; + } + + @Override + public boolean isAir() { + return defaultState.isAir(); + } + + @Override + public boolean isFullCube() { + return craftMaterial.isOccluding(); + } + + @Override + public boolean isOpaque() { + return material.f(); + } + + @Override + public boolean isPowerSource() { + return defaultState.isPowerSource(); + } + + @Override + public boolean isLiquid() { + return material.isLiquid(); + } + + @Override + public boolean isSolid() { + return material.isBuildable(); + } + + @Override + public float getHardness() { + return block.strength; + } + + @Override + public float getResistance() { + return block.getDurability(); + } + + @Override + public float getSlipperiness() { + return block.m(); + } + + @Override + public int getLightValue() { + return defaultState.h(); + } + + @Override + public int getLightOpacity() { + return isTranslucent() ? 15 : 0; + } + + @Override + public boolean isFragileWhenPushed() { + return material.getPushReaction() == EnumPistonReaction.DESTROY; + } + + @Override + public boolean isUnpushable() { + return material.getPushReaction() == EnumPistonReaction.BLOCK; + } + + @Override + public boolean isTicksRandomly() { + return block.isTicking(defaultState); + } + + @Override + public boolean isMovementBlocker() { + return material.isSolid(); + } + + @Override + public boolean isBurnable() { + return material.isBurnable(); + } + + @Override + public boolean isToolRequired() { + return !material.isAlwaysDestroyable(); + } + + @Override + public boolean isReplacedDuringPlacement() { + return material.isReplaceable(); + } + + @Override + public boolean isTranslucent() { + return isTranslucent; + } + + @Override + public boolean hasContainer() { + return block instanceof ITileEntity; + } + + @Override + public int getMapColor() { + return material.i().rgb; + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15_2/BukkitAdapter_1_15_2.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15_2/BukkitAdapter_1_15_2.java new file mode 100644 index 000000000..d0e617e20 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15_2/BukkitAdapter_1_15_2.java @@ -0,0 +1,255 @@ +package com.boydti.fawe.bukkit.adapter.mc1_15_2; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.FaweCache; +import com.boydti.fawe.bukkit.adapter.DelegateLock; +import com.boydti.fawe.bukkit.adapter.NMSAdapter; +import com.boydti.fawe.config.Settings; +import com.boydti.fawe.object.collection.BitArray4096; +import com.boydti.fawe.util.MathMan; +import com.boydti.fawe.util.ReflectionUtils; +import com.boydti.fawe.util.TaskManager; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockTypesCache; +import io.papermc.lib.PaperLib; +import net.jpountz.util.UnsafeUtils; +import net.minecraft.server.v1_15_R1.*; +import org.bukkit.craftbukkit.v1_15_R1.CraftChunk; +import org.bukkit.craftbukkit.v1_15_R1.CraftWorld; +import sun.misc.Unsafe; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.locks.Lock; +import java.util.function.Function; + +public final class BukkitAdapter_1_15_2 extends NMSAdapter { + /* + NMS fields + */ + public final static Field fieldBits; + public final static Field fieldPalette; + public final static Field fieldSize; + + public final static Field fieldFluidCount; + public final static Field fieldTickingBlockCount; + public final static Field fieldNonEmptyBlockCount; + + private final static Field fieldDirtyCount; + private final static Field fieldDirtyBits; + + private static final int CHUNKSECTION_BASE; + private static final int CHUNKSECTION_SHIFT; + + private static final Field fieldLock; + + static { + try { + fieldSize = DataPaletteBlock.class.getDeclaredField("i"); + fieldSize.setAccessible(true); + fieldBits = DataPaletteBlock.class.getDeclaredField("a"); + fieldBits.setAccessible(true); + fieldPalette = DataPaletteBlock.class.getDeclaredField("h"); + fieldPalette.setAccessible(true); + + fieldFluidCount = ChunkSection.class.getDeclaredField("e"); + fieldFluidCount.setAccessible(true); + fieldTickingBlockCount = ChunkSection.class.getDeclaredField("tickingBlockCount"); + fieldTickingBlockCount.setAccessible(true); + fieldNonEmptyBlockCount = ChunkSection.class.getDeclaredField("nonEmptyBlockCount"); + fieldNonEmptyBlockCount.setAccessible(true); + + fieldDirtyCount = PlayerChunk.class.getDeclaredField("dirtyCount"); + fieldDirtyCount.setAccessible(true); + fieldDirtyBits = PlayerChunk.class.getDeclaredField("r"); + fieldDirtyBits.setAccessible(true); + + Field tmp = DataPaletteBlock.class.getDeclaredField("j"); + ReflectionUtils.setAccessibleNonFinal(tmp); + fieldLock = tmp; + fieldLock.setAccessible(true); + + Unsafe unsafe = UnsafeUtils.getUNSAFE(); + CHUNKSECTION_BASE = unsafe.arrayBaseOffset(ChunkSection[].class); + int scale = unsafe.arrayIndexScale(ChunkSection[].class); + if ((scale & (scale - 1)) != 0) + throw new Error("data type scale not a power of two"); + CHUNKSECTION_SHIFT = 31 - Integer.numberOfLeadingZeros(scale); + } catch (RuntimeException e) { + throw e; + } catch (Throwable rethrow) { + rethrow.printStackTrace(); + throw new RuntimeException(rethrow); + } + } + + protected static boolean setSectionAtomic(ChunkSection[] sections, ChunkSection expected, ChunkSection value, int layer) { + long offset = ((long) layer << CHUNKSECTION_SHIFT) + CHUNKSECTION_BASE; + if (layer >= 0 && layer < sections.length) { + return UnsafeUtils.getUNSAFE().compareAndSwapObject(sections, offset, expected, value); + } + return false; + } + + protected static DelegateLock applyLock(ChunkSection section) { + try { + synchronized (section) { + DataPaletteBlock blocks = section.getBlocks(); + Lock currentLock = (Lock) fieldLock.get(blocks); + if (currentLock instanceof DelegateLock) { + return (DelegateLock) currentLock; + } + DelegateLock newLock = new DelegateLock(currentLock); + fieldLock.set(blocks, newLock); + return newLock; + } + } catch (IllegalAccessException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + + public static Chunk ensureLoaded(World nmsWorld, int X, int Z) { + Chunk nmsChunk = nmsWorld.getChunkIfLoaded(X, Z); + if (nmsChunk != null) { + return nmsChunk; + } + if (Fawe.isMainThread()) { + return nmsWorld.getChunkAt(X, Z); + } + if (PaperLib.isPaper()) { + CraftWorld craftWorld = nmsWorld.getWorld(); + CompletableFuture future = craftWorld.getChunkAtAsync(X, Z, true); + try { + CraftChunk chunk = (CraftChunk) future.get(); + return chunk.getHandle(); + } catch (Throwable e) { + e.printStackTrace(); + } + } + // TODO optimize + return TaskManager.IMP.sync(() -> nmsWorld.getChunkAt(X, Z)); + } + + public static PlayerChunk getPlayerChunk(WorldServer nmsWorld, final int cx, final int cz) { + PlayerChunkMap chunkMap = nmsWorld.getChunkProvider().playerChunkMap; + PlayerChunk playerChunk = chunkMap.visibleChunks.get(ChunkCoordIntPair.pair(cx, cz)); + return playerChunk; + } + + public static void sendChunk(WorldServer nmsWorld, int X, int Z, int mask) { + PlayerChunk playerChunk = getPlayerChunk(nmsWorld, X, Z); + if (playerChunk == null) { + return; + } + if (playerChunk.hasBeenLoaded()) { + TaskManager.IMP.sync(() -> { + try { + int dirtyBits = fieldDirtyBits.getInt(playerChunk); + if (dirtyBits == 0) { + nmsWorld.getChunkProvider().playerChunkMap.a(playerChunk); + } + if (mask == 0) { + dirtyBits = 65535; + } else { + dirtyBits |= mask; + } + + fieldDirtyBits.set(playerChunk, dirtyBits); + fieldDirtyCount.set(playerChunk, 64); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + return null; + }); + return; + } + return; + } + + /* + NMS conversion + */ + public static ChunkSection newChunkSection(final int layer, final char[] blocks) { + return newChunkSection(layer, null, blocks); + } + + public static ChunkSection newChunkSection(final int layer, final Function get, char[] set) { + if (set == null) { + return newChunkSection(layer); + } + final int[] blockToPalette = FaweCache.IMP.BLOCK_TO_PALETTE.get(); + final int[] paletteToBlock = FaweCache.IMP.PALETTE_TO_BLOCK.get(); + final long[] blockStates = FaweCache.IMP.BLOCK_STATES.get(); + final int[] blocksCopy = FaweCache.IMP.SECTION_BLOCKS.get(); + try { + int[] num_palette_buffer = new int[1]; + int air; + if (get == null) { + air = createPalette(blockToPalette, paletteToBlock, blocksCopy, num_palette_buffer, set); + } else { + air = createPalette(layer, blockToPalette, paletteToBlock, blocksCopy, num_palette_buffer, get, set); + } + int num_palette = num_palette_buffer[0]; + // BlockStates + int bitsPerEntry = MathMan.log2nlz(num_palette - 1); + if (Settings.IMP.PROTOCOL_SUPPORT_FIX || num_palette != 1) { + bitsPerEntry = Math.max(bitsPerEntry, 4); // Protocol support breaks <4 bits per entry + } else { + bitsPerEntry = Math.max(bitsPerEntry, 1); // For some reason minecraft needs 4096 bits to store 0 entries + } + + final int blockBitArrayEnd = (bitsPerEntry * 4096) >> 6; + if (num_palette == 1) { + for (int i = 0; i < blockBitArrayEnd; i++) blockStates[i] = 0; + } else { + final BitArray4096 bitArray = new BitArray4096(blockStates, bitsPerEntry); + bitArray.fromRaw(blocksCopy); + } + + ChunkSection section = newChunkSection(layer); + // set palette & data bits + final DataPaletteBlock dataPaletteBlocks = section.getBlocks(); + // private DataPalette h; + // protected DataBits a; + final long[] bits = Arrays.copyOfRange(blockStates, 0, blockBitArrayEnd); + final DataBits nmsBits = new DataBits(bitsPerEntry, 4096, bits); + final DataPalette palette; +// palette = new DataPaletteHash<>(Block.REGISTRY_ID, bitsPerEntry, dataPaletteBlocks, GameProfileSerializer::d, GameProfileSerializer::a); + palette = new DataPaletteLinear<>(Block.REGISTRY_ID, bitsPerEntry, dataPaletteBlocks, GameProfileSerializer::d); + + // set palette + for (int i = 0; i < num_palette; i++) { + final int ordinal = paletteToBlock[i]; + blockToPalette[ordinal] = Integer.MAX_VALUE; + final BlockState state = BlockTypesCache.states[ordinal]; + final IBlockData ibd = ((BlockMaterial_1_15_2) state.getMaterial()).getState(); + palette.a(ibd); + } + try { + fieldBits.set(dataPaletteBlocks, nmsBits); + fieldPalette.set(dataPaletteBlocks, palette); + fieldSize.set(dataPaletteBlocks, bitsPerEntry); + setCount(0, 4096 - air, section); + } catch (final IllegalAccessException | NoSuchFieldException e) { + throw new RuntimeException(e); + } + + return section; + } catch (final Throwable e) { + Arrays.fill(blockToPalette, Integer.MAX_VALUE); + throw e; + } + } + + private static ChunkSection newChunkSection(int layer) { + return new ChunkSection(layer << 4); + } + + public static void setCount(final int tickingBlockCount, final int nonEmptyBlockCount, final ChunkSection section) throws NoSuchFieldException, IllegalAccessException { + fieldFluidCount.setShort(section, (short) 0); // TODO FIXME + fieldTickingBlockCount.setShort(section, (short) tickingBlockCount); + fieldNonEmptyBlockCount.setShort(section, (short) nonEmptyBlockCount); + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15_2/BukkitGetBlocks_1_15_2.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15_2/BukkitGetBlocks_1_15_2.java new file mode 100644 index 000000000..5424c4495 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15_2/BukkitGetBlocks_1_15_2.java @@ -0,0 +1,618 @@ +package com.boydti.fawe.bukkit.adapter.mc1_15_2; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.FaweCache; +import com.boydti.fawe.beta.IChunkSet; +import com.boydti.fawe.beta.implementation.blocks.CharGetBlocks; +import com.boydti.fawe.beta.implementation.queue.QueueHandler; +import com.boydti.fawe.bukkit.adapter.DelegateLock; +import com.boydti.fawe.bukkit.adapter.mc1_15.nbt.LazyCompoundTag_1_15; +import com.boydti.fawe.bukkit.adapter.mc1_15_2.nbt.LazyCompoundTag_1_15_2; +import com.boydti.fawe.object.collection.AdaptedMap; +import com.boydti.fawe.object.collection.BitArray4096; +import com.boydti.fawe.util.ReflectionUtils; +import com.google.common.base.Suppliers; +import com.google.common.collect.Iterables; +import com.sk89q.jnbt.Tag; +import com.sk89q.jnbt.*; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; +import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; +import com.sk89q.worldedit.bukkit.adapter.impl.FAWE_Spigot_v1_15_R2; +import com.sk89q.worldedit.internal.Constants; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.world.biome.BiomeType; +import com.sk89q.worldedit.world.block.BlockTypes; +import net.minecraft.server.v1_15_R1.*; +import org.bukkit.World; +import org.bukkit.block.Biome; +import org.bukkit.craftbukkit.v1_15_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_15_R1.block.CraftBlock; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.jetbrains.annotations.NotNull; + +import javax.annotation.Nullable; +import java.util.*; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.function.Function; + +import static org.slf4j.LoggerFactory.getLogger; + +public class BukkitGetBlocks_1_15_2 extends CharGetBlocks { + private static final Function posNms2We = v -> BlockVector3.at(v.getX(), v.getY(), v.getZ()); + private final static Function nmsTile2We = tileEntity -> new LazyCompoundTag_1_15_2(Suppliers.memoize(() -> tileEntity.save(new NBTTagCompound()))); + public ChunkSection[] sections; + public Chunk nmsChunk; + public WorldServer world; + public int X, Z; + + public BukkitGetBlocks_1_15_2(World world, int X, int Z) { + this(((CraftWorld) world).getHandle(), X, Z); + } + + public BukkitGetBlocks_1_15_2(WorldServer world, int X, int Z) { + this.world = world; + this.X = X; + this.Z = Z; + } + + public int getX() { + return X; + } + + public int getZ() { + return Z; + } + + @Override + public BiomeType getBiomeType(int x, int y, int z) { + BiomeStorage index = getChunk().getBiomeIndex(); + BiomeBase base = null; + if (y == -1) { + for (y = 0; y < FaweCache.IMP.WORLD_HEIGHT; y++) { + base = index.getBiome(x, y, z); + if (base != null) break; + } + } else { + base = index.getBiome(x, y, z); + } + return base != null ? BukkitAdapter.adapt(CraftBlock.biomeBaseToBiome(base)) : null; + } + + @Override + public CompoundTag getTile(int x, int y, int z) { + TileEntity tileEntity = getChunk().getTileEntity(new BlockPosition((x & 15) + (X << 4), y, (z & 15) + (Z << 4))); + if (tileEntity == null) { + return null; + } + return new LazyCompoundTag_1_15_2(Suppliers.memoize(() -> tileEntity.save(new NBTTagCompound()))); + } + + @Override + public Map getTiles() { + Map nmsTiles = getChunk().getTileEntities(); + if (nmsTiles.isEmpty()) { + return Collections.emptyMap(); + } + return AdaptedMap.immutable(nmsTiles, posNms2We, nmsTile2We); + } + + @Override + public CompoundTag getEntity(UUID uuid) { + Entity entity = world.getEntity(uuid); + if (entity != null) { + org.bukkit.entity.Entity bukkitEnt = entity.getBukkitEntity(); + return BukkitAdapter.adapt(bukkitEnt).getState().getNbtData(); + } + for (List entry : getChunk().getEntitySlices()) { + if (entry != null) { + for (Entity ent : entry) { + if (uuid.equals(ent.getUniqueID())) { + org.bukkit.entity.Entity bukkitEnt = ent.getBukkitEntity(); + return BukkitAdapter.adapt(bukkitEnt).getState().getNbtData(); + } + } + } + } + return null; + } + + @Override + public Set getEntities() { + List[] slices = getChunk().getEntitySlices(); + int size = 0; + for (List slice : slices) { + if (slice != null) size += slice.size(); + } + if (slices.length == 0) { + return Collections.emptySet(); + } + int finalSize = size; + return new AbstractSet() { + @Override + public int size() { + return finalSize; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public boolean contains(Object get) { + if (!(get instanceof CompoundTag)) { + return false; + } + CompoundTag getTag = (CompoundTag) get; + Map value = getTag.getValue(); + CompoundTag getParts = (CompoundTag) value.get("UUID"); + UUID getUUID = new UUID(getParts.getLong("Most"), getParts.getLong("Least")); + for (List slice : slices) { + if (slice != null) { + for (Entity entity : slice) { + UUID uuid = entity.getUniqueID(); + if (uuid.equals(getUUID)) { + return true; + } + } + } + } + return false; + } + + @NotNull + @Override + public Iterator iterator() { + Iterable result = Iterables.transform(Iterables.concat(slices), new com.google.common.base.Function() { + @Nullable + @Override + public CompoundTag apply(@Nullable Entity input) { + BukkitImplAdapter adapter = WorldEditPlugin.getInstance().getBukkitImplAdapter(); + NBTTagCompound tag = new NBTTagCompound(); + return (CompoundTag) adapter.toNative(input.save(tag)); + } + }); + return result.iterator(); + } + }; + } + + private void updateGet(BukkitGetBlocks_1_15_2 get, Chunk nmsChunk, ChunkSection[] sections, ChunkSection section, char[] arr, int layer) { + synchronized (get) { + if (this.nmsChunk != nmsChunk) { + this.nmsChunk = nmsChunk; + this.sections = sections.clone(); + this.reset(); + } + if (this.sections == null) { + this.sections = sections.clone(); + } + if (this.sections[layer] != section) { + this.sections[layer] = section; + } + this.blocks[layer] = arr; + } + } + + private void removeEntity(Entity entity) { + entity.die(); + entity.valid = false; + } + + public Chunk ensureLoaded(net.minecraft.server.v1_15_R1.World nmsWorld, int X, int Z) { + return BukkitAdapter_1_15_2.ensureLoaded(nmsWorld, X, Z); + } + + @Override + public > T call(IChunkSet set, Runnable finalizer) { + try { + WorldServer nmsWorld = world; + Chunk nmsChunk = ensureLoaded(nmsWorld, X, Z); + + // Remove existing tiles + { + Map tiles = nmsChunk.getTileEntities(); + if (!tiles.isEmpty()) { + for (Map.Entry entry : tiles.entrySet()) { + final BlockPosition pos = entry.getKey(); + final int lx = pos.getX() & 15; + final int ly = pos.getY(); + final int lz = pos.getZ() & 15; + final int layer = ly >> 4; + if (!set.hasSection(layer)) { + continue; + } + if (set.getBlock(lx, ly, lz).getOrdinal() != 0) { + TileEntity tile = entry.getValue(); + tile.hasWorld(); + tile.invalidateBlockCache(); + } + } + } + } + + int bitMask = 0; + synchronized (nmsChunk) { + ChunkSection[] sections = nmsChunk.getSections(); + + for (int layer = 0; layer < 16; layer++) { + if (!set.hasSection(layer)){ + continue; + } + + bitMask |= 1 << layer; + + char[] setArr = set.load(layer); + ChunkSection newSection; + ChunkSection existingSection = sections[layer]; + if (existingSection == null) { + newSection = BukkitAdapter_1_15_2.newChunkSection(layer, setArr); + if (BukkitAdapter_1_15_2.setSectionAtomic(sections, null, newSection, layer)) { + updateGet(this, nmsChunk, sections, newSection, setArr, layer); + continue; + } else { + existingSection = sections[layer]; + if (existingSection == null) { + System.out.println("Skipping invalid null section. chunk:" + X + "," + Z + " layer: " + layer); + continue; + } + } + } + DelegateLock lock = BukkitAdapter_1_15_2.applyLock(existingSection); + synchronized (this) { + synchronized (lock) { + lock.untilFree(); + ChunkSection getSection; + if (this.nmsChunk != nmsChunk) { + this.nmsChunk = nmsChunk; + this.sections = null; + this.reset(); + } else { + getSection = this.getSections()[layer]; + if (getSection != existingSection) { + this.sections[layer] = existingSection; + this.reset(); + } else if (lock.isModified()) { + this.reset(layer); + } + } + newSection = BukkitAdapter_1_15_2.newChunkSection(layer, this::load, setArr); + if (!BukkitAdapter_1_15_2.setSectionAtomic(sections, existingSection, newSection, layer)) { + System.out.println("Failed to set chunk section:" + X + "," + Z + " layer: " + layer); + continue; + } else { + updateGet(this, nmsChunk, sections, newSection, setArr, layer); + } + } + } + } + + // Biomes + BiomeType[] biomes = set.getBiomes(); + if (biomes != null) { + // set biomes + BiomeStorage currentBiomes = nmsChunk.getBiomeIndex(); + for (int z = 0, i = 0; z < 16; z++) { + for (int x = 0; x < 16; x++, i++) { + final BiomeType biome = biomes[i]; + if (biome != null) { + final Biome craftBiome = BukkitAdapter.adapt(biome); + BiomeBase nmsBiome = CraftBlock.biomeToBiomeBase(craftBiome); + for (int y = 0; y < FaweCache.IMP.WORLD_HEIGHT; y++) { + currentBiomes.setBiome(x, y, z, nmsBiome); + } + } + } + } + } + + Runnable[] syncTasks = null; + + int bx = X << 4; + int bz = Z << 4; + + Set entityRemoves = set.getEntityRemoves(); + if (entityRemoves != null && !entityRemoves.isEmpty()) { + if (syncTasks == null) syncTasks = new Runnable[3]; + + syncTasks[2] = () -> { + final List[] entities = nmsChunk.getEntitySlices(); + + for (final Collection ents : entities) { + if (!ents.isEmpty()) { + final Iterator iter = ents.iterator(); + while (iter.hasNext()) { + final Entity entity = iter.next(); + if (entityRemoves.contains(entity.getUniqueID())) { + iter.remove(); + removeEntity(entity); + } + } + } + } + }; + } + + Set entities = set.getEntities(); + if (entities != null && !entities.isEmpty()) { + if (syncTasks == null) syncTasks = new Runnable[2]; + + syncTasks[1] = () -> { + for (final CompoundTag nativeTag : entities) { + final Map entityTagMap = ReflectionUtils.getMap(nativeTag.getValue()); + final StringTag idTag = (StringTag) entityTagMap.get("Id"); + final ListTag posTag = (ListTag) entityTagMap.get("Pos"); + final ListTag rotTag = (ListTag) entityTagMap.get("Rotation"); + if (idTag == null || posTag == null || rotTag == null) { + getLogger(BukkitGetBlocks_1_15_2.class).debug("Unknown entity tag: " + nativeTag); + continue; + } + final double x = posTag.getDouble(0); + final double y = posTag.getDouble(1); + final double z = posTag.getDouble(2); + final float yaw = rotTag.getFloat(0); + final float pitch = rotTag.getFloat(1); + final String id = idTag.getValue(); + + EntityTypes type = EntityTypes.a(id).orElse(null); + if (type != null) { + Entity entity = type.a(nmsWorld); + if (entity != null) { + UUID uuid = entity.getUniqueID(); + entityTagMap.put("UUIDMost", new LongTag(uuid.getMostSignificantBits())); + entityTagMap.put("UUIDLeast", new LongTag(uuid.getLeastSignificantBits())); + if (nativeTag != null) { + BukkitImplAdapter adapter = WorldEditPlugin.getInstance().getBukkitImplAdapter(); + final NBTTagCompound tag = (NBTTagCompound) adapter.fromNative(nativeTag); + for (final String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) { + tag.remove(name); + } + entity.f(tag); + } + entity.setLocation(x, y, z, yaw, pitch); + nmsWorld.addEntity(entity, CreatureSpawnEvent.SpawnReason.CUSTOM); + } + } + } + }; + + } + + // set tiles + Map tiles = set.getTiles(); + if (tiles != null && !tiles.isEmpty()) { + if (syncTasks == null) syncTasks = new Runnable[1]; + + syncTasks[0] = () -> { + for (final Map.Entry entry : tiles.entrySet()) { + final CompoundTag nativeTag = entry.getValue(); + final BlockVector3 blockHash = entry.getKey(); + final int x = blockHash.getX() + bx; + final int y = blockHash.getY(); + final int z = blockHash.getZ() + bz; + final BlockPosition pos = new BlockPosition(x, y, z); + + synchronized (nmsWorld) { + TileEntity tileEntity = nmsWorld.getTileEntity(pos); + if (tileEntity == null || tileEntity.isRemoved()) { + nmsWorld.removeTileEntity(pos); + tileEntity = nmsWorld.getTileEntity(pos); + } + if (tileEntity != null) { + BukkitImplAdapter adapter = WorldEditPlugin.getInstance().getBukkitImplAdapter(); + final NBTTagCompound tag = (NBTTagCompound) adapter.fromNative(nativeTag); + tag.set("x", NBTTagInt.a(x)); + tag.set("y", NBTTagInt.a(y)); + tag.set("z", NBTTagInt.a(z)); + tileEntity.load(tag); + } + } + } + }; + } + + //Lighting + // TODO optimize, cause this is really slow + LightEngineThreaded engine = (LightEngineThreaded) nmsChunk.e(); + engine.a(nmsChunk, false); + + Runnable callback; + if (bitMask == 0 && biomes == null) { + callback = null; + } else { + int finalMask = bitMask; + callback = () -> { + // Set Modified + nmsChunk.d(true); // Set Modified + nmsChunk.mustNotSave = false; + nmsChunk.markDirty(); + // send to player + BukkitAdapter_1_15_2.sendChunk(nmsWorld, X, Z, finalMask); + if (finalizer != null) finalizer.run(); + }; + } + if (syncTasks != null) { + QueueHandler queueHandler = Fawe.get().getQueueHandler(); + Runnable[] finalSyncTasks = syncTasks; + + // Chain the sync tasks and the callback + Callable chain = () -> { + try { + // Run the sync tasks + for (Runnable task : finalSyncTasks) { + if (task != null) { + task.run(); + } + } + if (callback == null) { + if (finalizer != null) finalizer.run(); + return null; + } else { + return queueHandler.async(callback, null); + } + } catch (Throwable e) { + e.printStackTrace(); + throw e; + } + }; + return (T) (Future) queueHandler.sync(chain); + } else { + if (callback == null) { + if (finalizer != null) finalizer.run(); + } else { + callback.run(); + } + } + } + return null; + } catch (Throwable e) { + e.printStackTrace(); + return null; + } + } + + @Override + public synchronized char[] update(int layer, char[] data) { + ChunkSection section = getSections()[layer]; + // Section is null, return empty array + if (section == null) { + return FaweCache.IMP.EMPTY_CHAR_4096; + } + if (data == null || data == FaweCache.IMP.EMPTY_CHAR_4096) { + data = new char[4096]; + } + DelegateLock lock = BukkitAdapter_1_15_2.applyLock(section); + synchronized (lock) { + lock.untilFree(); + lock.setModified(false); + // Efficiently convert ChunkSection to raw data + try { + FAWE_Spigot_v1_15_R2 adapter = ((FAWE_Spigot_v1_15_R2) WorldEditPlugin.getInstance().getBukkitImplAdapter()); + + final DataPaletteBlock blocks = section.getBlocks(); + final DataBits bits = (DataBits) BukkitAdapter_1_15_2.fieldBits.get(blocks); + final DataPalette palette = (DataPalette) BukkitAdapter_1_15_2.fieldPalette.get(blocks); + + final int bitsPerEntry = bits.c(); + final long[] blockStates = bits.a(); + + new BitArray4096(blockStates, bitsPerEntry).toRaw(data); + + int num_palette; + if (palette instanceof DataPaletteLinear) { + num_palette = ((DataPaletteLinear) palette).b(); + } else if (palette instanceof DataPaletteHash) { + num_palette = ((DataPaletteHash) palette).b(); + } else { + num_palette = 0; + int[] paletteToBlockInts = FaweCache.IMP.PALETTE_TO_BLOCK.get(); + char[] paletteToBlockChars = FaweCache.IMP.PALETTE_TO_BLOCK_CHAR.get(); + try { + for (int i = 0; i < 4096; i++) { + char paletteVal = data[i]; + char ordinal = paletteToBlockChars[paletteVal]; + if (ordinal == Character.MAX_VALUE) { + paletteToBlockInts[num_palette++] = paletteVal; + IBlockData ibd = palette.a(data[i]); + if (ibd == null) { + ordinal = BlockTypes.AIR.getDefaultState().getOrdinalChar(); + } else { + ordinal = adapter.adaptToChar(ibd); + } + paletteToBlockChars[paletteVal] = ordinal; + } + data[i] = ordinal; + } + } finally { + for (int i = 0; i < num_palette; i++) { + int paletteVal = paletteToBlockInts[i]; + paletteToBlockChars[paletteVal] = Character.MAX_VALUE; + } + } + return data; + } + + char[] paletteToOrdinal = FaweCache.IMP.PALETTE_TO_BLOCK_CHAR.get(); + try { + if (num_palette != 1) { + for (int i = 0; i < num_palette; i++) { + char ordinal = ordinal(palette.a(i), adapter); + paletteToOrdinal[i] = ordinal; + } + for (int i = 0; i < 4096; i++) { + char paletteVal = data[i]; + char val = paletteToOrdinal[paletteVal]; + if (val == Character.MAX_VALUE) { + val = ordinal(palette.a(i), adapter); + paletteToOrdinal[i] = val; + } + data[i] = val; + } + } else { + char ordinal = ordinal(palette.a(0), adapter); + Arrays.fill(data, ordinal); + } + } finally { + for (int i = 0; i < num_palette; i++) { + paletteToOrdinal[i] = Character.MAX_VALUE; + } + } + return data; + } catch (IllegalAccessException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + } + + private final char ordinal(IBlockData ibd, FAWE_Spigot_v1_15_R2 adapter) { + if (ibd == null) { + return BlockTypes.AIR.getDefaultState().getOrdinalChar(); + } else { + return adapter.adaptToChar(ibd); + } + } + + public ChunkSection[] getSections() { + ChunkSection[] tmp = sections; + if (tmp == null) { + synchronized (this) { + tmp = sections; + if (tmp == null) { + Chunk chunk = getChunk(); + sections = tmp = chunk.getSections().clone(); + } + } + } + return tmp; + } + + public Chunk getChunk() { + Chunk tmp = nmsChunk; + if (tmp == null) { + synchronized (this) { + tmp = nmsChunk; + if (tmp == null) { + nmsChunk = tmp = ensureLoaded(this.world, X, Z); + } + } + } + return tmp; + } + + @Override + public boolean hasSection(int layer) { + return getSections()[layer] != null; + } + + @Override + public boolean trim(boolean aggressive) { + if (aggressive) { + sections = null; + nmsChunk = null; + } + return super.trim(aggressive); + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15_2/MapChunkUtil_1_15_2.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15_2/MapChunkUtil_1_15_2.java new file mode 100644 index 000000000..e12648f4a --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15_2/MapChunkUtil_1_15_2.java @@ -0,0 +1,28 @@ +package com.boydti.fawe.bukkit.adapter.mc1_15_2; + +import com.boydti.fawe.bukkit.adapter.MapChunkUtil; +import net.minecraft.server.v1_15_R1.PacketPlayOutMapChunk; + +public class MapChunkUtil_1_15_2 extends MapChunkUtil { + public MapChunkUtil_1_15_2() throws NoSuchFieldException { + fieldX = PacketPlayOutMapChunk.class.getDeclaredField("a"); + fieldZ = PacketPlayOutMapChunk.class.getDeclaredField("b"); + fieldBitMask = PacketPlayOutMapChunk.class.getDeclaredField("c"); + fieldHeightMap = PacketPlayOutMapChunk.class.getDeclaredField("d"); + fieldChunkData = PacketPlayOutMapChunk.class.getDeclaredField("e"); + fieldBlockEntities = PacketPlayOutMapChunk.class.getDeclaredField("f"); + fieldFull = PacketPlayOutMapChunk.class.getDeclaredField("g"); + fieldX.setAccessible(true); + fieldZ.setAccessible(true); + fieldBitMask.setAccessible(true); + fieldHeightMap.setAccessible(true); + fieldChunkData.setAccessible(true); + fieldBlockEntities.setAccessible(true); + fieldFull.setAccessible(true); + } + + @Override + public PacketPlayOutMapChunk createPacket() { + return new PacketPlayOutMapChunk(); + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15_2/nbt/LazyCompoundTag_1_15_2.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15_2/nbt/LazyCompoundTag_1_15_2.java new file mode 100644 index 000000000..4b603d3b6 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_15_2/nbt/LazyCompoundTag_1_15_2.java @@ -0,0 +1,152 @@ +package com.boydti.fawe.bukkit.adapter.mc1_15_2.nbt; + +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.jnbt.ListTag; +import com.sk89q.jnbt.StringTag; +import com.sk89q.jnbt.Tag; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; +import net.minecraft.server.v1_15_R1.NBTBase; +import net.minecraft.server.v1_15_R1.NBTNumber; +import net.minecraft.server.v1_15_R1.NBTTagCompound; +import net.minecraft.server.v1_15_R1.NBTTagList; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +public class LazyCompoundTag_1_15_2 extends CompoundTag { + private final Supplier nmsTag; + + public LazyCompoundTag_1_15_2(Supplier tag) { + super(null); + this.nmsTag = tag; + } + + public LazyCompoundTag_1_15_2(NBTTagCompound tag) { + this(() -> tag); + } + + public NBTTagCompound get() { + return nmsTag.get(); + } + + @Override + public Map getValue() { + Map value = super.getValue(); + if (value == null) { + Tag tag = WorldEditPlugin.getInstance().getBukkitImplAdapter().toNative(nmsTag.get()); + setValue(((CompoundTag) tag).getValue()); + } + return super.getValue(); + } + + public boolean containsKey(String key) { + return nmsTag.get().hasKey(key); + } + + public byte[] getByteArray(String key) { + return nmsTag.get().getByteArray(key); + } + + public byte getByte(String key) { + return nmsTag.get().getByte(key); + } + + public double getDouble(String key) { + return nmsTag.get().getDouble(key); + } + + public double asDouble(String key) { + NBTBase value = nmsTag.get().get(key); + if (value instanceof NBTNumber) { + return ((NBTNumber) value).asDouble(); + } + return 0; + } + + public float getFloat(String key) { + return nmsTag.get().getFloat(key); + } + + public int[] getIntArray(String key) { + return nmsTag.get().getIntArray(key); + } + + public int getInt(String key) { + return nmsTag.get().getInt(key); + } + + public int asInt(String key) { + NBTBase value = nmsTag.get().get(key); + if (value instanceof NBTNumber) { + return ((NBTNumber) value).asInt(); + } + return 0; + } + + public List getList(String key) { + NBTBase tag = nmsTag.get().get(key); + if (tag instanceof NBTTagList) { + ArrayList list = new ArrayList<>(); + NBTTagList nbtList = (NBTTagList) tag; + for (NBTBase elem : nbtList) { + if (elem instanceof NBTTagCompound) { + list.add(new LazyCompoundTag_1_15_2((NBTTagCompound) elem)); + } else { + list.add(WorldEditPlugin.getInstance().getBukkitImplAdapter().toNative(elem)); + } + } + return list; + } + return Collections.emptyList(); + } + + public ListTag getListTag(String key) { + NBTBase tag = nmsTag.get().get(key); + if (tag instanceof NBTTagList) { + return (ListTag) WorldEditPlugin.getInstance().getBukkitImplAdapter().toNative(tag); + } + return new ListTag(StringTag.class, Collections.emptyList()); + } + + @SuppressWarnings("unchecked") + public List getList(String key, Class listType) { + ListTag listTag = getListTag(key); + if (listTag.getType().equals(listType)) { + return (List) listTag.getValue(); + } else { + return Collections.emptyList(); + } + } + + public long[] getLongArray(String key) { + return nmsTag.get().getLongArray(key); + } + + public long getLong(String key) { + return nmsTag.get().getLong(key); + } + + public long asLong(String key) { + NBTBase value = nmsTag.get().get(key); + if (value instanceof NBTNumber) { + return ((NBTNumber) value).asLong(); + } + return 0; + } + + public short getShort(String key) { + return nmsTag.get().getShort(key); + } + + public String getString(String key) { + return nmsTag.get().getString(key); + } + + @Override + public String toString() { + return nmsTag.get().toString(); + } +} \ No newline at end of file diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java index 92bf39673..6c8fdbadb 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java @@ -36,6 +36,7 @@ import com.sk89q.worldedit.bukkit.adapter.AdapterLoadException; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; import com.sk89q.worldedit.bukkit.adapter.BukkitImplLoader; import com.sk89q.worldedit.bukkit.adapter.impl.FAWE_Spigot_v1_15_R1; +import com.sk89q.worldedit.bukkit.adapter.impl.FAWE_Spigot_v1_15_R2; import com.sk89q.worldedit.event.platform.CommandEvent; import com.sk89q.worldedit.event.platform.CommandSuggestionEvent; import com.sk89q.worldedit.event.platform.PlatformReadyEvent; @@ -372,6 +373,7 @@ public class WorldEditPlugin extends JavaPlugin { //implements TabCompleter try { adapterLoader.addClass(FAWE_Spigot_v1_14_R4.class); adapterLoader.addClass(FAWE_Spigot_v1_15_R1.class); + adapterLoader.addClass(FAWE_Spigot_v1_15_R2.class); } catch (Throwable throwable) { throwable.printStackTrace(); } diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_15_R2.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_15_R2.java new file mode 100644 index 000000000..86b9ad81f --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_15_R2.java @@ -0,0 +1,451 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.bukkit.adapter.impl; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.FaweCache; +import com.boydti.fawe.beta.IChunkGet; +import com.boydti.fawe.beta.IQueueChunk; +import com.boydti.fawe.beta.IQueueExtent; +import com.boydti.fawe.beta.implementation.packet.ChunkPacket; +import com.boydti.fawe.beta.implementation.queue.SingleThreadQueueExtent; +import com.boydti.fawe.bukkit.adapter.mc1_15_2.BlockMaterial_1_15_2; +import com.boydti.fawe.bukkit.adapter.mc1_15_2.BukkitAdapter_1_15_2; +import com.boydti.fawe.bukkit.adapter.mc1_15_2.BukkitGetBlocks_1_15_2; +import com.boydti.fawe.bukkit.adapter.mc1_15_2.MapChunkUtil_1_15_2; +import com.boydti.fawe.bukkit.adapter.mc1_15_2.nbt.LazyCompoundTag_1_15_2; +import com.google.common.io.Files; +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.jnbt.Tag; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; +import com.sk89q.worldedit.blocks.BaseItemStack; +import com.sk89q.worldedit.blocks.TileEntityBlock; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; +import com.sk89q.worldedit.bukkit.adapter.CachedBukkitAdapter; +import com.sk89q.worldedit.bukkit.adapter.IDelegateBukkitImplAdapter; +import com.sk89q.worldedit.entity.BaseEntity; +import com.sk89q.worldedit.entity.LazyBaseEntity; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.registry.state.Property; +import com.sk89q.worldedit.world.biome.BiomeType; +import com.sk89q.worldedit.world.biome.BiomeTypes; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.*; +import com.sk89q.worldedit.world.entity.EntityType; +import com.sk89q.worldedit.world.registry.BlockMaterial; +import net.minecraft.server.v1_15_R1.*; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.block.data.BlockData; +import org.bukkit.craftbukkit.v1_15_R1.CraftChunk; +import org.bukkit.craftbukkit.v1_15_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_15_R1.block.CraftBlock; +import org.bukkit.craftbukkit.v1_15_R1.block.data.CraftBlockData; +import org.bukkit.craftbukkit.v1_15_R1.entity.CraftEntity; +import org.bukkit.craftbukkit.v1_15_R1.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_15_R1.inventory.CraftItemStack; +import org.bukkit.entity.Player; +import org.bukkit.generator.ChunkGenerator; + +import javax.annotation.Nullable; +import java.io.File; +import java.io.IOException; +import java.util.Map; +import java.util.OptionalInt; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import static com.google.common.base.Preconditions.checkNotNull; + +public final class FAWE_Spigot_v1_15_R2 extends CachedBukkitAdapter implements IDelegateBukkitImplAdapter { + private final Spigot_v1_15_R2 parent; + + // ------------------------------------------------------------------------ + // Code that may break between versions of Minecraft + // ------------------------------------------------------------------------ + + public FAWE_Spigot_v1_15_R2() throws NoSuchFieldException, NoSuchMethodException { + this.parent = new Spigot_v1_15_R2(); + } + + @Override + public BukkitImplAdapter getParent() { + return parent; + } + + public char[] idbToStateOrdinal; + + private synchronized boolean init() { + if (idbToStateOrdinal != null) return false; + idbToStateOrdinal = new char[Block.REGISTRY_ID.a()]; // size + for (int i = 0; i < idbToStateOrdinal.length; i++) { + BlockState state = BlockTypesCache.states[i]; + BlockMaterial_1_15_2 material = (BlockMaterial_1_15_2) state.getMaterial(); + int id = Block.REGISTRY_ID.getId(material.getState()); + idbToStateOrdinal[id] = state.getOrdinalChar(); + } + return true; + } + + @Override + public BlockMaterial getMaterial(BlockType blockType) { + Block block = getBlock(blockType); + return new BlockMaterial_1_15_2(block); + } + + @Override + public BlockMaterial getMaterial(BlockState state) { + IBlockData bs = ((CraftBlockData) Bukkit.createBlockData(state.getAsString())).getState(); + return new BlockMaterial_1_15_2(bs.getBlock(), bs); + + + } + + public Block getBlock(BlockType blockType) { + return IRegistry.BLOCK.get(new MinecraftKey(blockType.getNamespace(), blockType.getResource())); + } + + @SuppressWarnings("deprecation") + @Override + public BaseBlock getBlock(Location location) { + checkNotNull(location); + + CraftWorld craftWorld = ((CraftWorld) location.getWorld()); + int x = location.getBlockX(); + int y = location.getBlockY(); + int z = location.getBlockZ(); + + final WorldServer handle = craftWorld.getHandle(); + Chunk chunk = handle.getChunkAt(x >> 4, z >> 4); + final BlockPosition blockPos = new BlockPosition(x, y, z); + org.bukkit.block.Block bukkitBlock = location.getBlock(); + BlockState state = BukkitAdapter.adapt(bukkitBlock.getBlockData()); + if (state.getBlockType().getMaterial().hasContainer()) { + + // Read the NBT data + TileEntity te = chunk.a(blockPos, Chunk.EnumTileEntityState.CHECK); + if (te != null) { + NBTTagCompound tag = new NBTTagCompound(); + te.save(tag); // readTileEntityIntoTag - load data + return state.toBaseBlock((CompoundTag) toNative(tag)); + } + } + + return state.toBaseBlock(); + } + + @Override + public > boolean setBlock(Location location, B state, boolean notifyAndLight) { + return this.setBlock(location.getChunk(), location.getBlockX(), location.getBlockY(), location.getBlockZ(), state, notifyAndLight); + } + + public > boolean setBlock(org.bukkit.Chunk chunk, int x, int y, int z, B state, boolean update) { + CraftChunk craftChunk = (CraftChunk) chunk; + Chunk nmsChunk = craftChunk.getHandle(); + World nmsWorld = nmsChunk.getWorld(); + + BlockPosition blockPos = new BlockPosition(x, y, z); + IBlockData blockData = ((BlockMaterial_1_15_2) state.getMaterial()).getState(); + ChunkSection[] sections = nmsChunk.getSections(); + int y4 = y >> 4; + ChunkSection section = sections[y4]; + + IBlockData existing; + if (section == null) { + existing = ((BlockMaterial_1_15_2) BlockTypes.AIR.getDefaultState().getMaterial()).getState(); + } else { + existing = section.getType(x & 15, y & 15, z & 15); + } + + + nmsChunk.removeTileEntity(blockPos); // Force delete the old tile entity + + CompoundTag nativeTag = state instanceof BaseBlock ? ((BaseBlock)state).getNbtData() : null; + if (nativeTag != null || existing instanceof TileEntityBlock) { + nmsWorld.setTypeAndData(blockPos, blockData, 0); + // remove tile + if (nativeTag != null) { + // We will assume that the tile entity was created for us, + // though we do not do this on the Forge version + TileEntity tileEntity = nmsWorld.getTileEntity(blockPos); + if (tileEntity != null) { + NBTTagCompound tag = (NBTTagCompound) fromNative(nativeTag); + tag.set("x", NBTTagInt.a(x)); + tag.set("y", NBTTagInt.a(y)); + tag.set("z", NBTTagInt.a(z)); + tileEntity.load(tag); // readTagIntoTileEntity - load data + } + } + } else { + if (existing == blockData) return true; + if (section == null) { + if (blockData.isAir()) return true; + sections[y4] = section = new ChunkSection(y4 << 4); + } + nmsChunk.setType(blockPos, blockData, false); + } + if (update) { + nmsWorld.getMinecraftWorld().notify(blockPos, existing, blockData, 0); + } + return true; + } + + @Nullable + private static String getEntityId(Entity entity) { + MinecraftKey minecraftkey = EntityTypes.getName(entity.getEntityType()); + return minecraftkey == null ? null : minecraftkey.toString(); + } + + private static void readEntityIntoTag(Entity entity, NBTTagCompound tag) { + entity.save(tag); + } + + @Override + public BaseEntity getEntity(org.bukkit.entity.Entity entity) { + checkNotNull(entity); + + CraftEntity craftEntity = ((CraftEntity) entity); + Entity mcEntity = craftEntity.getHandle(); + + String id = getEntityId(mcEntity); + + if (id != null) { + EntityType type = com.sk89q.worldedit.world.entity.EntityTypes.get(id); + Supplier saveTag = () -> { + NBTTagCompound tag = new NBTTagCompound(); + readEntityIntoTag(mcEntity, tag); + return (CompoundTag) toNative(tag); + }; + return new LazyBaseEntity(type, saveTag); + } else { + return null; + } + } + + @Override + public OptionalInt getInternalBlockStateId(BlockState state) { + BlockMaterial_1_15_2 material = (BlockMaterial_1_15_2) state.getMaterial(); + IBlockData mcState = material.getCraftBlockData().getState(); + return OptionalInt.of(Block.REGISTRY_ID.getId(mcState)); + } + + @Override + public BlockState adapt(BlockData blockData) { + CraftBlockData cbd = ((CraftBlockData) blockData); + IBlockData ibd = cbd.getState(); + return adapt(ibd); + } + + public BlockState adapt(IBlockData ibd) { + return BlockTypesCache.states[adaptToInt(ibd)]; + } + + public int adaptToInt(IBlockData ibd) { + try { + int id = Block.REGISTRY_ID.getId(ibd); + return idbToStateOrdinal[id]; + } catch (NullPointerException e) { + init(); + return adaptToInt(ibd); + } + } + + public char adaptToChar(IBlockData ibd) { + try { + int id = Block.REGISTRY_ID.getId(ibd); + return idbToStateOrdinal[id]; + } catch (NullPointerException e) { + init(); + return adaptToChar(ibd); + } + } + + @Override + public > BlockData adapt(B state) { + BlockMaterial_1_15_2 material = (BlockMaterial_1_15_2) state.getMaterial(); + return material.getCraftBlockData(); + } + + @Override + public void notifyAndLightBlock(Location position, BlockState previousType) { + this.setBlock(position.getChunk(), position.getBlockX(), position.getBlockY(), position.getBlockZ(), previousType, true); + } + + private MapChunkUtil_1_15_2 mapUtil = new MapChunkUtil_1_15_2(); + + @Override + public void sendFakeChunk(org.bukkit.World world, Player player, ChunkPacket packet) { + WorldServer nmsWorld = ((CraftWorld) world).getHandle(); + PlayerChunk map = BukkitAdapter_1_15_2.getPlayerChunk(nmsWorld, packet.getChunkX(), packet.getChunkZ()); + if (map != null && map.hasBeenLoaded()) { + boolean flag = false; + PlayerChunk.d players = map.players; + Stream stream = players.a(new ChunkCoordIntPair(packet.getChunkX(), packet.getChunkZ()), flag); + + EntityPlayer checkPlayer = player == null ? null : ((CraftPlayer) player).getHandle(); + stream.filter(entityPlayer -> checkPlayer == null || entityPlayer == checkPlayer) + .forEach(entityPlayer -> { + synchronized (packet) { + PacketPlayOutMapChunk nmsPacket = (PacketPlayOutMapChunk) packet.getNativePacket(); + if (nmsPacket == null) { + nmsPacket = mapUtil.create( this, packet); + packet.setNativePacket(nmsPacket); + } + try { + FaweCache.IMP.CHUNK_FLAG.get().set(true); + entityPlayer.playerConnection.sendPacket(nmsPacket); + } finally { + FaweCache.IMP.CHUNK_FLAG.get().set(false); + } + } + }); + } + } + + @Override + public Map> getProperties(BlockType blockType) { + return getParent().getProperties(blockType); + } + + @Override + public org.bukkit.inventory.ItemStack adapt(BaseItemStack item) { + ItemStack stack = new ItemStack(IRegistry.ITEM.get(MinecraftKey.a(item.getType().getId())), item.getAmount()); + stack.setTag(((NBTTagCompound) fromNative(item.getNbtData()))); + return CraftItemStack.asCraftMirror(stack); + } + + @Override + public BaseItemStack adapt(org.bukkit.inventory.ItemStack itemStack) { + final ItemStack nmsStack = CraftItemStack.asNMSCopy(itemStack); + final BaseItemStack weStack = new BaseItemStack(BukkitAdapter.asItemType(itemStack.getType()), itemStack.getAmount()); + weStack.setNbtData(((CompoundTag) toNative(nmsStack.getTag()))); + return weStack; + } + + @Override + public Tag toNative(NBTBase foreign) { + return parent.toNative(foreign); + } + + @Override + public NBTBase fromNative(Tag foreign) { + if (foreign instanceof LazyCompoundTag_1_15_2) { + return ((LazyCompoundTag_1_15_2) foreign).get(); + } + return parent.fromNative(foreign); + } + + @Override + public boolean regenerate(org.bukkit.World world, Region region, @Nullable Long seed, @Nullable BiomeType biome, EditSession editSession) { + WorldServer originalWorld = ((CraftWorld) world).getHandle(); + ChunkProviderServer provider = originalWorld.getChunkProvider(); + if (!(provider instanceof ChunkProviderServer)) { + return false; + } + + File saveFolder = Files.createTempDir(); + // register this just in case something goes wrong + // normally it should be deleted at the end of this method + saveFolder.deleteOnExit(); + try { + MinecraftServer server = originalWorld.getServer().getServer(); + WorldNBTStorage originalDataManager = originalWorld.getDataManager(); + WorldNBTStorage saveHandler = new WorldNBTStorage(saveFolder, originalDataManager.getDirectory().getName(), server, originalDataManager.getDataFixer()); + WorldData newWorldData = new WorldData(originalWorld.worldData.a((NBTTagCompound) null), + server.dataConverterManager, getDataVersion(), null); + newWorldData.setName(UUID.randomUUID().toString()); + + ChunkGenerator generator = world.getGenerator(); + org.bukkit.World.Environment environment = world.getEnvironment(); + if (seed != null) { + if (biome == BiomeTypes.NETHER) { + environment = org.bukkit.World.Environment.NETHER; + } else if (biome == BiomeTypes.THE_END) { + environment = org.bukkit.World.Environment.THE_END; + } else { + environment = org.bukkit.World.Environment.NORMAL; + } + generator = null; + } + try (WorldServer freshWorld = new WorldServer(server, + server.executorService, saveHandler, + newWorldData, + originalWorld.worldProvider.getDimensionManager(), + originalWorld.getMethodProfiler(), + server.worldLoadListenerFactory.create(11), + environment, + generator)) { + + // Pre-gen all the chunks + // We need to also pull one more chunk in every direction + Fawe.get().getQueueHandler().startSet(true); + try { + IQueueExtent extent = new SingleThreadQueueExtent(); + extent.init(null, (x, z) -> new BukkitGetBlocks_1_15_2(freshWorld, x, z) { + @Override + public Chunk ensureLoaded(World nmsWorld, int X, int Z) { + Chunk cached = nmsWorld.getChunkIfLoaded(X, Z); + if (cached != null) return cached; + Future future = Fawe.get().getQueueHandler().sync((Supplier) () -> freshWorld.getChunkAt(X, Z)); + while (!future.isDone()) { + // this feels so dirty + freshWorld.getChunkProvider().runTasks(); + } + try { + return future.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + }, null); + for (BlockVector3 vec : region) { + editSession.setBlock(vec, extent.getFullBlock(vec)); + } + } finally { + Fawe.get().getQueueHandler().endSet(true); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } catch (MaxChangedBlocksException e) { + throw new RuntimeException(e); + } finally { + saveFolder.delete(); + } + return true; + } + + @Override + public IChunkGet get(org.bukkit.World world, int chunkX, int chunkZ) { + return new BukkitGetBlocks_1_15_2(world, chunkX, chunkZ); + } + + @Override + public int getInternalBiomeId(BiomeType biome) { + BiomeBase base = CraftBlock.biomeToBiomeBase(BukkitAdapter.adapt(biome)); + return IRegistry.BIOME.a(base); + } +}