From 49052d73ce7320c3ffc99a1a4ceafe49af5e2283 Mon Sep 17 00:00:00 2001 From: Aurora <21148213+aurorasmiles@users.noreply.github.com> Date: Tue, 3 Nov 2020 19:46:54 +0100 Subject: [PATCH] Update FAWE to 1.16.4 (#734) * Start work on 1.16.4, doesn't compile yet * Update FAWE to 1.16.4 * Update GitHub ci build to 1.16.4 * Fix AsyncChunk * Update issue template for 1.16.4 Co-authored-by: dordsor21 Co-authored-by: NotMyFault --- .github/ISSUE_TEMPLATE/bug-report.md | 4 +- .github/workflows/gradle.yml | 2 +- worldedit-bukkit/build.gradle.kts | 5 +- .../mc1_16_4/BlockMaterial_1_16_4.java | 160 ++++ .../mc1_16_4/BukkitAdapter_1_16_4.java | 326 +++++++ .../mc1_16_4/BukkitGetBlocks_1_16_4.java | 855 ++++++++++++++++++ .../mc1_16_4/BukkitGetBlocks_1_16_4_Copy.java | 130 +++ .../mc1_16_4/FAWEWorldNativeAccess_1_16.java | 176 ++++ .../adapter/mc1_16_4/MapChunkUtil_1_16_4.java | 28 + .../mc1_16_4/nbt/LazyCompoundTag_1_16_4.java | 152 ++++ .../fawe/bukkit/wrapper/AsyncChunk.java | 20 +- .../adapter/impl/FAWE_Spigot_v1_16_R3.java | 420 +++++++++ .../adapter/impl/regen/Regen_v1_16_R3.java | 579 ++++++++++++ 13 files changed, 2847 insertions(+), 10 deletions(-) create mode 100644 worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/BlockMaterial_1_16_4.java create mode 100644 worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/BukkitAdapter_1_16_4.java create mode 100644 worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/BukkitGetBlocks_1_16_4.java create mode 100644 worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/BukkitGetBlocks_1_16_4_Copy.java create mode 100644 worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/FAWEWorldNativeAccess_1_16.java create mode 100644 worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/MapChunkUtil_1_16_4.java create mode 100644 worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/nbt/LazyCompoundTag_1_16_4.java create mode 100644 worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_16_R3.java create mode 100644 worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/regen/Regen_v1_16_R3.java diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md index 87c970593..a6086b9a6 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -24,7 +24,7 @@ assignees: '' **Required Information** - FAWE Version Number (`/version FastAsyncWorldEdit`): - Spigot/Paper Version Number (`/version`): -- Minecraft Version: [e.g. 1.16.3] +- Minecraft Version: [e.g. 1.16.4] **Describe the bug** A clear and concise description of what the bug is. @@ -43,5 +43,5 @@ Steps to reproduce the behavior: - [] I included all information required in the sections above - [] I made sure there are no duplicates of this report [(Use Search)](https://github.com/IntellectualSites/FastAsyncWorldEdit/issues?q=is%3Aissue) -- [] I made sure I am using an up-to-date version of [FastAsyncWorldEdit for 1.16.3](https://ci.athion.net/job/FastAsyncWorldEdit-1.16/) +- [] I made sure I am using an up-to-date version of [FastAsyncWorldEdit for 1.16.4](https://ci.athion.net/job/FastAsyncWorldEdit-1.16/) - [] I made sure the bug/error is not caused by any other plugin diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 5f45bcd47..3e22a4ebb 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -50,7 +50,7 @@ jobs: - name: Download BuildTools run: wget https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar - name: Run BuildTools - run: java -jar BuildTools.jar --rev 1.16.2 + run: java -jar BuildTools.jar --rev 1.16.4 - name: Test with Gradle run: ./gradlew clean build sourcesJar javadocJar diff --git a/worldedit-bukkit/build.gradle.kts b/worldedit-bukkit/build.gradle.kts index d592b694e..6bba93ae2 100644 --- a/worldedit-bukkit/build.gradle.kts +++ b/worldedit-bukkit/build.gradle.kts @@ -41,14 +41,15 @@ dependencies { "compile"(":worldedit-adapters:") "compile"("org.spigotmcv1_15_r1:spigotmcv1_15_r1:1_15_r1") "compile"("org.spigotmcv1_16_r1:spigotmcv1_16_r1:1_16_r1") + "compile"("org.spigotmcv1_16_r2:spigotmcv1_16_r2:1_16_r2") "implementation"("it.unimi.dsi:fastutil:${Versions.FAST_UTIL}") - "api"("com.destroystokyo.paper:paper-api:1.16.2-R0.1-SNAPSHOT") { + "api"("com.destroystokyo.paper:paper-api:1.16.4-R0.1-SNAPSHOT") { exclude("junit", "junit") isTransitive = false } "compileOnly"("org.jetbrains:annotations:20.1.0") "testCompileOnly"("org.jetbrains:annotations:20.1.0") - "compileOnly"("org.spigotmc:spigot:1.16.2-R0.1-SNAPSHOT") + "compileOnly"("org.spigotmc:spigot:1.16.4-R0.1-SNAPSHOT") "implementation"("io.papermc:paperlib:1.0.4") "compileOnly"("com.sk89q:dummypermscompat:1.10") { exclude("com.github.MilkBowl", "VaultAPI") diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/BlockMaterial_1_16_4.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/BlockMaterial_1_16_4.java new file mode 100644 index 000000000..f3d40b448 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/BlockMaterial_1_16_4.java @@ -0,0 +1,160 @@ +package com.boydti.fawe.bukkit.adapter.mc1_16_4; + +import com.sk89q.util.ReflectionUtil; +import com.sk89q.worldedit.world.registry.BlockMaterial; +import net.minecraft.server.v1_16_R3.Block; +import net.minecraft.server.v1_16_R3.BlockAccessAir; +import net.minecraft.server.v1_16_R3.BlockBase; +import net.minecraft.server.v1_16_R3.BlockPosition; +import net.minecraft.server.v1_16_R3.EnumPistonReaction; +import net.minecraft.server.v1_16_R3.IBlockData; +import net.minecraft.server.v1_16_R3.ITileEntity; +import net.minecraft.server.v1_16_R3.Material; +import org.bukkit.craftbukkit.v1_16_R3.block.data.CraftBlockData; + +public class BlockMaterial_1_16_4 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; + private final int opacity; + + public BlockMaterial_1_16_4(Block block) { + this(block, block.getBlockData()); + } + + public BlockMaterial_1_16_4(Block block, IBlockData defaultState) { + this.block = block; + this.defaultState = defaultState; + this.material = defaultState.getMaterial(); + this.craftBlockData = CraftBlockData.fromData(defaultState); + this.craftMaterial = craftBlockData.getMaterial(); + BlockBase.Info blockInfo = ReflectionUtil.getField(Block.class, block, "aB"); + this.isTranslucent = !(boolean)ReflectionUtil.getField(BlockBase.Info.class, blockInfo, "n"); + opacity = defaultState.b(BlockAccessAir.INSTANCE, BlockPosition.ZERO); + } + + 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 craftBlockData.getState().strength; + } + + @Override + public float getResistance() { + return block.getDurability(); + } + + @Override + public float getSlipperiness() { + return block.getFrictionFactor(); + } + + @Override + public int getLightValue() { + return defaultState.f(); + } + + @Override + public int getLightOpacity() { + return opacity; + } + + @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() { + //TODO Removed in 1.16.1 Replacement not found. + return true; + } + + @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.h().rgb; + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/BukkitAdapter_1_16_4.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/BukkitAdapter_1_16_4.java new file mode 100644 index 000000000..d9f89cf2c --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/BukkitAdapter_1_16_4.java @@ -0,0 +1,326 @@ +package com.boydti.fawe.bukkit.adapter.mc1_16_4; + +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.BitArrayUnstretched; +import com.boydti.fawe.util.MathMan; +import com.boydti.fawe.util.ReflectionUtils; +import com.boydti.fawe.util.TaskManager; +import com.mojang.datafixers.util.Either; +import com.sk89q.worldedit.math.BlockVector3; +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_16_R3.BiomeBase; +import net.minecraft.server.v1_16_R3.BiomeStorage; +import net.minecraft.server.v1_16_R3.Block; +import net.minecraft.server.v1_16_R3.Chunk; +import net.minecraft.server.v1_16_R3.ChunkCoordIntPair; +import net.minecraft.server.v1_16_R3.ChunkSection; +import net.minecraft.server.v1_16_R3.DataBits; +import net.minecraft.server.v1_16_R3.DataPalette; +import net.minecraft.server.v1_16_R3.DataPaletteBlock; +import net.minecraft.server.v1_16_R3.DataPaletteLinear; +import net.minecraft.server.v1_16_R3.GameProfileSerializer; +import net.minecraft.server.v1_16_R3.IBlockData; +import net.minecraft.server.v1_16_R3.PacketPlayOutLightUpdate; +import net.minecraft.server.v1_16_R3.PacketPlayOutMapChunk; +import net.minecraft.server.v1_16_R3.PlayerChunk; +import net.minecraft.server.v1_16_R3.PlayerChunkMap; +import net.minecraft.server.v1_16_R3.World; +import net.minecraft.server.v1_16_R3.WorldServer; +import org.bukkit.craftbukkit.v1_16_R3.CraftChunk; +import org.bukkit.craftbukkit.v1_16_R3.CraftWorld; +import sun.misc.Unsafe; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Function; + +public final class BukkitAdapter_1_16_4 extends NMSAdapter { + /* + NMS fields + */ + public static final Field fieldBits; + public static final Field fieldPalette; + public static final Field fieldSize; + + public static final Field fieldBitsPerEntry; + + public static final Field fieldFluidCount; + public static final Field fieldTickingBlockCount; + public static final Field fieldNonEmptyBlockCount; + + private static final Field fieldDirty; + private static final Field fieldDirtyBlocks; + + private static final Field fieldBiomeArray; + + private static final MethodHandle methodGetVisibleChunk; + + 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); + + fieldBitsPerEntry = DataBits.class.getDeclaredField("c"); + fieldBitsPerEntry.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); + + fieldDirty = PlayerChunk.class.getDeclaredField("p"); + fieldDirty.setAccessible(true); + fieldDirtyBlocks = PlayerChunk.class.getDeclaredField("dirtyBlocks"); + fieldDirtyBlocks.setAccessible(true); + + fieldBiomeArray = BiomeStorage.class.getDeclaredField("h"); + fieldBiomeArray.setAccessible(true); + + Method declaredGetVisibleChunk = PlayerChunkMap.class.getDeclaredMethod("getVisibleChunk", long.class); + declaredGetVisibleChunk.setAccessible(true); + methodGetVisibleChunk = MethodHandles.lookup().unreflect(declaredGetVisibleChunk); + + 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); + + Class clsShortArraySet; + try { //paper + clsShortArraySet = Class.forName(new String(new char[]{'i', 't', '.', 'u', 'n', 'i', 'm', 'i', '.', 'd', 's', 'i', '.', 'f', 'a', 's', 't', 'u', 't', 'i', 'l', '.', 's', 'h', 'o', 'r', 't', 's', '.', 'S', 'h', 'o', 'r', 't', 'A', 'r', 'r', 'a', 'y', 'S', 'e', 't'})); + } catch (Throwable t) { // still using spigot boo + clsShortArraySet = Class.forName(new String(new char[]{'o', 'r', 'g', '.', 'b', 'u', 'k', 'k', 'i', 't', '.', 'c', 'r', 'a', 'f', 't', 'b', 'u', 'k', 'k', 'i', 't', '.', 'l', 'i', 'b', 's', '.', 'i', 't', '.', 'u', 'n', 'i', 'm', 'i', '.', 'd', 's', 'i', '.', 'f', 'a', 's', 't', 'u', 't', 'i', 'l', '.', 's', 'h', 'o', 'r', 't', 's', '.', 'S', 'h', 'o', 'r', 't', 'A', 'r', 'r', 'a', 'y', 'S', 'e', 't'})); + } + } 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) { + //todo there has to be a better way to do this. Maybe using a() in DataPaletteBlock which acquires the lock in NMS? + try { + synchronized (section) { + DataPaletteBlock blocks = section.getBlocks(); + ReentrantLock currentLock = (ReentrantLock) 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 chunkX, int chunkZ) { + Chunk nmsChunk = nmsWorld.getChunkProvider().getChunkAt(chunkX, chunkZ, false); + if (nmsChunk != null) { + return nmsChunk; + } + if (Fawe.isMainThread()) { + return nmsWorld.getChunkAt(chunkX, chunkZ); + } + if (PaperLib.isPaper()) { + CraftWorld craftWorld = nmsWorld.getWorld(); + CompletableFuture future = craftWorld.getChunkAtAsync(chunkX, chunkZ, true); + try { + CraftChunk chunk = (CraftChunk) future.get(); + return chunk.getHandle(); + } catch (Throwable e) { + e.printStackTrace(); + } + } + // TODO optimize + return TaskManager.IMP.sync(() -> nmsWorld.getChunkAt(chunkX, chunkZ)); + } + + public static PlayerChunk getPlayerChunk(WorldServer nmsWorld, final int chunkX, final int chunkZ) { + PlayerChunkMap chunkMap = nmsWorld.getChunkProvider().playerChunkMap; + try { + return (PlayerChunk) methodGetVisibleChunk.invoke(chunkMap, ChunkCoordIntPair.pair(chunkX, chunkZ)); + } catch (Throwable thr) { + throw new RuntimeException(thr); + } + } + + public static void sendChunk(WorldServer nmsWorld, int chunkX, int chunkZ, int mask, boolean lighting) { + PlayerChunk playerChunk = getPlayerChunk(nmsWorld, chunkX, chunkZ); + if (playerChunk == null) { + return; + } + if (playerChunk.hasBeenLoaded()) { + TaskManager.IMP.sync(() -> { + ChunkCoordIntPair chunkCoordIntPair = new ChunkCoordIntPair(chunkX, chunkZ); + Optional optional = ((Either) playerChunk.a().getNow(PlayerChunk.UNLOADED_CHUNK)).left(); + if (optional.isPresent()) { + PacketPlayOutMapChunk chunkpacket = new PacketPlayOutMapChunk(optional.get(), 65535); + playerChunk.players.a(chunkCoordIntPair, false).forEach(p -> { + p.playerConnection.sendPacket(chunkpacket); + }); + } + + if (lighting) { + boolean trustEdges = true; //This needs to be true otherwise Minecraft will update lighting from/at the chunk edges (bad) + PacketPlayOutLightUpdate packet = new PacketPlayOutLightUpdate(chunkCoordIntPair, nmsWorld.getChunkProvider().getLightEngine(), trustEdges); + playerChunk.players.a(chunkCoordIntPair, false).forEach(p -> { + p.playerConnection.sendPacket(packet); + }); + } + return null; + }); + } + } + + /* + NMS conversion + */ + public static ChunkSection newChunkSection(final int layer, final char[] blocks, boolean fastmode) { + return newChunkSection(layer, null, blocks, fastmode); + } + + public static ChunkSection newChunkSection(final int layer, final Function get, char[] set, boolean fastmode) { + 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]; + Map ticking_blocks = new HashMap<>(); + int air; + if (get == null) { + air = createPalette(blockToPalette, paletteToBlock, blocksCopy, num_palette_buffer, + set, ticking_blocks, fastmode); + } else { + air = createPalette(layer, blockToPalette, paletteToBlock, blocksCopy, + num_palette_buffer, get, set, ticking_blocks, fastmode); + } + 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 blocksPerLong = MathMan.floorZero((double) 64 / bitsPerEntry); + final int blockBitArrayEnd = MathMan.ceilZero((float) 4096 / blocksPerLong); + + if (num_palette == 1) { + for (int i = 0; i < blockBitArrayEnd; i++) { + blockStates[i] = 0; + } + } else { + final BitArrayUnstretched bitArray = new BitArrayUnstretched(bitsPerEntry, 4096, blockStates); + 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 DataPaletteLinear<>(Block.REGISTRY_ID, bitsPerEntry, dataPaletteBlocks, GameProfileSerializer::c); + + // 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_16_4) state.getMaterial()).getState(); + palette.a(ibd); + } + try { + fieldBits.set(dataPaletteBlocks, nmsBits); + fieldPalette.set(dataPaletteBlocks, palette); + fieldSize.set(dataPaletteBlocks, bitsPerEntry); + setCount(ticking_blocks.size(), 4096 - air, section); + if (!fastmode) { + ticking_blocks.forEach((pos, ordinal) -> section + .setType(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ(), + Block.getByCombinedId(ordinal))); + } + } catch (final IllegalAccessException 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 IllegalAccessException { + fieldFluidCount.setShort(section, (short) 0); // TODO FIXME + fieldTickingBlockCount.setShort(section, (short) tickingBlockCount); + fieldNonEmptyBlockCount.setShort(section, (short) nonEmptyBlockCount); + } + + public static BiomeBase[] getBiomeArray(BiomeStorage storage) { + try { + return (BiomeBase[]) fieldBiomeArray.get(storage); + } catch (IllegalAccessException e) { + e.printStackTrace(); + return null; + } + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/BukkitGetBlocks_1_16_4.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/BukkitGetBlocks_1_16_4.java new file mode 100644 index 000000000..962fd3a7b --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/BukkitGetBlocks_1_16_4.java @@ -0,0 +1,855 @@ +package com.boydti.fawe.bukkit.adapter.mc1_16_4; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.FaweCache; +import com.boydti.fawe.beta.IChunkGet; +import com.boydti.fawe.beta.IChunkSet; +import com.boydti.fawe.beta.implementation.blocks.CharBlocks; +import com.boydti.fawe.beta.implementation.blocks.CharGetBlocks; +import com.boydti.fawe.beta.implementation.lighting.HeightMapType; +import com.boydti.fawe.beta.implementation.queue.QueueHandler; +import com.boydti.fawe.bukkit.adapter.DelegateLock; +import com.boydti.fawe.bukkit.adapter.mc1_16_4.nbt.LazyCompoundTag_1_16_4; +import com.boydti.fawe.config.Settings; +import com.boydti.fawe.object.collection.AdaptedMap; +import com.boydti.fawe.object.collection.BitArrayUnstretched; +import com.google.common.base.Suppliers; +import com.google.common.collect.Iterables; +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.BukkitAdapter; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; +import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; +import com.sk89q.worldedit.bukkit.adapter.impl.FAWE_Spigot_v1_16_R3; +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_16_R3.BiomeBase; +import net.minecraft.server.v1_16_R3.BiomeStorage; +import net.minecraft.server.v1_16_R3.BlockPosition; +import net.minecraft.server.v1_16_R3.Chunk; +import net.minecraft.server.v1_16_R3.ChunkSection; +import net.minecraft.server.v1_16_R3.DataBits; +import net.minecraft.server.v1_16_R3.DataPalette; +import net.minecraft.server.v1_16_R3.DataPaletteBlock; +import net.minecraft.server.v1_16_R3.DataPaletteHash; +import net.minecraft.server.v1_16_R3.DataPaletteLinear; +import net.minecraft.server.v1_16_R3.Entity; +import net.minecraft.server.v1_16_R3.EntityTypes; +import net.minecraft.server.v1_16_R3.EnumSkyBlock; +import net.minecraft.server.v1_16_R3.HeightMap; +import net.minecraft.server.v1_16_R3.IBlockData; +import net.minecraft.server.v1_16_R3.IRegistry; +import net.minecraft.server.v1_16_R3.LightEngine; +import net.minecraft.server.v1_16_R3.NBTTagCompound; +import net.minecraft.server.v1_16_R3.NBTTagInt; +import net.minecraft.server.v1_16_R3.NibbleArray; +import net.minecraft.server.v1_16_R3.SectionPosition; +import net.minecraft.server.v1_16_R3.TileEntity; +import net.minecraft.server.v1_16_R3.WorldServer; +import org.bukkit.World; +import org.bukkit.block.Biome; +import org.bukkit.craftbukkit.v1_16_R3.CraftWorld; +import org.bukkit.craftbukkit.v1_16_R3.block.CraftBlock; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nullable; +import java.util.AbstractSet; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +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_16_4 extends CharGetBlocks { + + private static final Logger log = LoggerFactory.getLogger(BukkitGetBlocks_1_16_4.class); + + private static final Function posNms2We = v -> BlockVector3.at(v.getX(), v.getY(), v.getZ()); + private static final Function nmsTile2We = tileEntity -> new LazyCompoundTag_1_16_4(Suppliers.memoize(() -> tileEntity.save(new NBTTagCompound()))); + public ChunkSection[] sections; + public Chunk nmsChunk; + public WorldServer world; + public int chunkX; + public int chunkZ; + public NibbleArray[] blockLight = new NibbleArray[16]; + public NibbleArray[] skyLight = new NibbleArray[16]; + private boolean createCopy = false; + private BukkitGetBlocks_1_16_4_Copy copy = null; + + public BukkitGetBlocks_1_16_4(World world, int chunkX, int chunkZ) { + this(((CraftWorld) world).getHandle(), chunkX, chunkZ); + } + + public BukkitGetBlocks_1_16_4(WorldServer world, int chunkX, int chunkZ) { + this.world = world; + this.chunkX = chunkX; + this.chunkZ = chunkZ; + } + + public int getChunkX() { + return chunkX; + } + + @Override + public void setCreateCopy(boolean createCopy) { + this.createCopy = createCopy; + } + + @Override + public boolean isCreateCopy() { + return createCopy; + } + + @Override + public IChunkGet getCopy() { + return copy; + } + + public int getChunkZ() { + return chunkZ; + } + + @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 >> 2, y >> 2, z >> 2); + if (base != null) { + break; + } + } + } else { + base = index.getBiome(x >> 2, y >> 2, z >> 2); + } + return base != null ? BukkitAdapter.adapt(CraftBlock.biomeBaseToBiome(world.r().b(IRegistry.ay), base)) : null; + } + + @Override + public CompoundTag getTile(int x, int y, int z) { + TileEntity tileEntity = getChunk().getTileEntity(new BlockPosition((x & 15) + ( + chunkX << 4), y, (z & 15) + ( + chunkZ << 4))); + if (tileEntity == null) { + return null; + } + return new LazyCompoundTag_1_16_4(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 int getSkyLight(int x, int y, int z) { + int layer = y >> 4; + if (skyLight[layer] == null) { + SectionPosition sectionPosition = SectionPosition.a(nmsChunk.getPos(), layer); + NibbleArray nibbleArray = world.getChunkProvider().getLightEngine().a(EnumSkyBlock.SKY).a(sectionPosition); + // If the server hasn't generated the section's NibbleArray yet, it will be null + if (nibbleArray == null) { + byte[] a = new byte[2048]; + // Safe enough to assume if it's not created, it's under the sky. Unlikely to be created before lighting is fixed anyway. + Arrays.fill(a, (byte) 15); + nibbleArray = new NibbleArray(a); + ((LightEngine) world.getChunkProvider().getLightEngine()).a(EnumSkyBlock.SKY, sectionPosition, nibbleArray, true); + } + skyLight[layer] = nibbleArray; + } + long l = BlockPosition.a(x, y, z); + return skyLight[layer].a(SectionPosition.b(BlockPosition.b(l)), SectionPosition.b(BlockPosition.c(l)), SectionPosition.b(BlockPosition.d(l))); + } + + @Override + public int getEmmittedLight(int x, int y, int z) { + int layer = y >> 4; + if (blockLight[layer] == null) { + SectionPosition sectionPosition = SectionPosition.a(nmsChunk.getPos(), layer); + NibbleArray nibbleArray = world.getChunkProvider().getLightEngine().a(EnumSkyBlock.BLOCK).a(sectionPosition); + // If the server hasn't generated the section's NibbleArray yet, it will be null + if (nibbleArray == null) { + byte[] a = new byte[2048]; + // Safe enough to assume if it's not created, it's under the sky. Unlikely to be created before lighting is fixed anyway. + Arrays.fill(a, (byte) 15); + nibbleArray = new NibbleArray(a); + ((LightEngine) world.getChunkProvider().getLightEngine()).a(EnumSkyBlock.BLOCK, sectionPosition, nibbleArray, true); + } + blockLight[layer] = nibbleArray; + } + long l = BlockPosition.a(x, y, z); + return blockLight[layer].a(SectionPosition.b(BlockPosition.b(l)), SectionPosition.b(BlockPosition.c(l)), SectionPosition.b(BlockPosition.d(l))); + } + + @Override + public int[] getHeightMap(HeightMapType type) { + long[] longArray = getChunk().heightMap.get(HeightMap.Type.valueOf(type.name())).a(); + BitArrayUnstretched bitArray = new BitArrayUnstretched(9, 256, longArray); + return bitArray.toRaw(new int[256]); + } + + @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_16_4 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] = new ChunkSection[]{section}.clone()[0]; + } + this.blocks[layer] = arr; + } + } + + private void removeEntity(Entity entity) { + entity.die(); + } + + public Chunk ensureLoaded(net.minecraft.server.v1_16_R3.World nmsWorld, int chunkX, int chunkZ) { + return BukkitAdapter_1_16_4.ensureLoaded(nmsWorld, chunkX, chunkZ); + } + + @Override + public > T call(IChunkSet set, Runnable finalizer) { + copy = createCopy ? new BukkitGetBlocks_1_16_4_Copy(world, getChunkX(), getChunkZ()) : null; + try { + WorldServer nmsWorld = world; + Chunk nmsChunk = ensureLoaded(nmsWorld, chunkX, chunkZ); + boolean fastmode = set.isFastMode() && Settings.IMP.QUEUE.NO_TICK_FASTMODE; + + // Remove existing tiles + { + // Create a copy so that we can remove blocks + Map tiles = new HashMap<>(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; + } + + int ordinal = set.getBlock(lx, ly, lz).getOrdinal(); + if (ordinal != 0) { + TileEntity tile = entry.getValue(); + nmsChunk.removeTileEntity(tile.getPosition()); + if (createCopy) { + copy.storeTile(tile); + } + } + } + } + } + + int bitMask = 0; + synchronized (nmsChunk) { + ChunkSection[] sections = nmsChunk.getSections(); + + for (int layer = 0; layer < 16; layer++) { + if (!set.hasSection(layer)) { + continue; + } + if (createCopy) { + copy.storeSection(layer); + } + + bitMask |= 1 << layer; + + char[] setArr = set.load(layer); + ChunkSection newSection; + ChunkSection existingSection = sections[layer]; + if (existingSection == null) { + newSection = BukkitAdapter_1_16_4.newChunkSection(layer, setArr, fastmode); + if (BukkitAdapter_1_16_4.setSectionAtomic(sections, null, newSection, layer)) { + updateGet(this, nmsChunk, sections, newSection, setArr, layer); + continue; + } else { + existingSection = sections[layer]; + if (existingSection == null) { + log.error("Skipping invalid null section. chunk:" + chunkX + "," + + chunkZ + " layer: " + layer); + continue; + } + } + } + BukkitAdapter_1_16_4.fieldTickingBlockCount.set(existingSection, (short) 0); + + //ensure that the server doesn't try to tick the chunksection while we're editing it. + DelegateLock lock = BukkitAdapter_1_16_4.applyLock(existingSection); + + synchronized (this) { + synchronized (lock) { + lock.untilFree(); + if (this.nmsChunk != nmsChunk) { + this.nmsChunk = nmsChunk; + this.sections = null; + this.reset(); + } else if (existingSection != getSections()[layer]) { + this.sections[layer] = existingSection; + this.reset(); + } else if (!Arrays.equals(update(layer, new char[4096]), load(layer))) { + this.reset(layer); + } else if (lock.isModified()) { + this.reset(layer); + } + newSection = BukkitAdapter_1_16_4 + .newChunkSection(layer, this::load, setArr, fastmode); + if (!BukkitAdapter_1_16_4 + .setSectionAtomic(sections, existingSection, newSection, layer)) { + log.error("Failed to set chunk section:" + chunkX + "," + chunkZ + " 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(); + if (createCopy) { + copy.storeBiomes(currentBiomes); + } + for (int y = 0, i = 0; y < 64; y++) { + for (int z = 0; z < 4; z++) { + for (int x = 0; x < 4; x++, i++) { + final BiomeType biome = biomes[i]; + if (biome != null) { + final Biome craftBiome = BukkitAdapter.adapt(biome); + BiomeBase nmsBiome = CraftBlock.biomeToBiomeBase(nmsWorld.r().b(IRegistry.ay), craftBiome); + currentBiomes.setBiome(x, y, z, nmsBiome); + } + } + } + } + } + + Map heightMaps = set.getHeightMaps(); + for (Map.Entry entry : heightMaps.entrySet()) { + BitArrayUnstretched bitArray = new BitArrayUnstretched(9, 256); + bitArray.fromRaw(entry.getValue()); + nmsChunk.heightMap.get(HeightMap.Type.valueOf(entry.getKey().name())).a(bitArray.getData()); + } + + boolean lightUpdate = false; + + // Lighting + char[][] light = set.getLight(); + if (light != null) { + lightUpdate = true; + try { + fillLightNibble(light, EnumSkyBlock.BLOCK); + } catch (Throwable e) { + e.printStackTrace(); + } + } + + char[][] skyLight = set.getSkyLight(); + if (skyLight != null) { + lightUpdate = true; + try { + fillLightNibble(skyLight, EnumSkyBlock.SKY); + } catch (Throwable e) { + e.printStackTrace(); + } + } + + Runnable[] syncTasks = null; + + int bx = chunkX << 4; + int bz = chunkZ << 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())) { + if (createCopy) { + copy.storeEntity(entity); + } + 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 = 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_16_4.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) { + 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.load(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(tileEntity.getBlock(), tag); + } + } + } + }; + } + + Runnable callback; + if (bitMask == 0 && biomes == null && !lightUpdate) { + callback = null; + } else { + int finalMask = bitMask != 0 ? bitMask : lightUpdate ? set.getBitMask() : 0; + boolean finalLightUpdate = lightUpdate; + callback = () -> { + // Set Modified + nmsChunk.d(true); // Set Modified + nmsChunk.mustNotSave = false; + nmsChunk.markDirty(); + // send to player + BukkitAdapter_1_16_4.sendChunk(nmsWorld, chunkX, chunkZ, finalMask, finalLightUpdate); + 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) { + data = new char[4096]; + Arrays.fill(data, (char) 1); + return data; + } + if (data == null || data == FaweCache.IMP.EMPTY_CHAR_4096) { + data = new char[4096]; + Arrays.fill(data, (char) 1); + } + DelegateLock lock = BukkitAdapter_1_16_4.applyLock(section); + synchronized (lock) { + lock.untilFree(); + lock.setModified(false); + // Efficiently convert ChunkSection to raw data + try { + FAWE_Spigot_v1_16_R3 adapter = ((FAWE_Spigot_v1_16_R3) WorldEditPlugin.getInstance().getBukkitImplAdapter()); + + final DataPaletteBlock blocks = section.getBlocks(); + final DataBits bits = (DataBits) BukkitAdapter_1_16_4.fieldBits.get(blocks); + final DataPalette palette = (DataPalette) BukkitAdapter_1_16_4.fieldPalette.get(blocks); + + final int bitsPerEntry = (int) BukkitAdapter_1_16_4.fieldBitsPerEntry.get(bits); + final long[] blockStates = bits.a(); + + new BitArrayUnstretched(bitsPerEntry, 4096, blockStates).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; + } + // Don't read "empty". + if (ordinal == 0) { + ordinal = 1; + } + 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; + } + // Don't read "empty". + if (val == 0) { + val = 1; + } + data[i] = val; + } + } else { + char ordinal = ordinal(palette.a(0), adapter); + // Don't read "empty". + if (ordinal == 0) { + ordinal = 1; + } + 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_16_R3 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, chunkX, chunkZ); + } + } + } + return tmp; + } + + private void fillLightNibble(char[][] light, EnumSkyBlock skyBlock) { + for (int Y = 0; Y < 16; Y++) { + if (light[Y] == null) { + continue; + } + SectionPosition sectionPosition = SectionPosition.a(nmsChunk.getPos(), Y); + NibbleArray nibble = world.getChunkProvider().getLightEngine().a(skyBlock).a(sectionPosition); + if (nibble == null) { + byte[] a = new byte[2048]; + Arrays.fill(a, skyBlock == EnumSkyBlock.SKY ? (byte) 15 : (byte) 0); + nibble = new NibbleArray(a); + ((LightEngine) world.getChunkProvider().getLightEngine()).a(skyBlock, sectionPosition, nibble, true); + } + synchronized (nibble) { + for (int i = 0; i < 4096; i++) { + if (light[Y][i] < 16) { + nibble.a(i, light[Y][i]); + } + } + } + } + } + + @Override + public boolean hasSection(int layer) { + return getSections()[layer] != null; + } + + @Override + public boolean trim(boolean aggressive) { + skyLight = new NibbleArray[16]; + blockLight = new NibbleArray[16]; + if (aggressive) { + sections = null; + nmsChunk = null; + return super.trim(true); + } else { + for (int i = 0; i < 16; i++) { + if (!hasSection(i) || super.sections[i] == CharBlocks.EMPTY) { + continue; + } + ChunkSection existing = getSections()[i]; + try { + final DataPaletteBlock blocksExisting = existing.getBlocks(); + + final DataPalette palette = (DataPalette) BukkitAdapter_1_16_4.fieldPalette.get(blocksExisting); + int paletteSize; + + if (palette instanceof DataPaletteLinear) { + paletteSize = ((DataPaletteLinear) palette).b(); + } else if (palette instanceof DataPaletteHash) { + paletteSize = ((DataPaletteHash) palette).b(); + } else { + super.trim(false, i); + continue; + } + if (paletteSize == 1) { + //If the cached palette size is 1 then no blocks can have been changed i.e. do not need to update these chunks. + continue; + } + super.trim(false, i); + } catch (IllegalAccessException ignored) { + super.trim(false, i); + } + } + return true; + } + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/BukkitGetBlocks_1_16_4_Copy.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/BukkitGetBlocks_1_16_4_Copy.java new file mode 100644 index 000000000..2eec240cb --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/BukkitGetBlocks_1_16_4_Copy.java @@ -0,0 +1,130 @@ +package com.boydti.fawe.bukkit.adapter.mc1_16_4; + + +import com.boydti.fawe.FaweCache; +import com.boydti.fawe.bukkit.adapter.mc1_16_4.nbt.LazyCompoundTag_1_16_4; +import com.google.common.base.Suppliers; +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; +import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.world.biome.BiomeType; +import com.sk89q.worldedit.world.block.BaseBlock; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockTypesCache; +import net.minecraft.server.v1_16_R3.BiomeBase; +import net.minecraft.server.v1_16_R3.BiomeStorage; +import net.minecraft.server.v1_16_R3.Entity; +import net.minecraft.server.v1_16_R3.IRegistry; +import net.minecraft.server.v1_16_R3.NBTTagCompound; +import net.minecraft.server.v1_16_R3.TileEntity; +import net.minecraft.server.v1_16_R3.WorldServer; +import org.bukkit.craftbukkit.v1_16_R3.block.CraftBlock; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +public class BukkitGetBlocks_1_16_4_Copy extends BukkitGetBlocks_1_16_4 { + + private final Map tiles = new HashMap<>(); + private final Set entities = new HashSet<>(); + private BiomeStorage biomeStorage; + private final char[][] blocks = new char[16][4096]; + + protected BukkitGetBlocks_1_16_4_Copy(WorldServer world, int X, int Z) { + super(world, X, Z); + } + + protected void storeTile(TileEntity tile) { + tiles.put(BlockVector3.at(tile.getPosition().getX(), tile.getPosition().getY(), tile.getPosition().getZ()), + new LazyCompoundTag_1_16_4(Suppliers.memoize(() -> tile.save(new NBTTagCompound())))); + } + + @Override + public Map getTiles() { + return tiles; + } + + @Override + @Nullable + public CompoundTag getTile(int x, int y, int z) { + return tiles.get(BlockVector3.at(x, y, z)); + } + + protected void storeEntity(Entity entity) { + BukkitImplAdapter adapter = WorldEditPlugin.getInstance().getBukkitImplAdapter(); + NBTTagCompound tag = new NBTTagCompound(); + entities.add((CompoundTag) adapter.toNative(entity.save(tag))); + } + + @Override + public Set getEntities() { + return this.entities; + } + + @Override + public CompoundTag getEntity(UUID uuid) { + for (CompoundTag tag : entities) { + UUID tagUUID; + if (tag.containsKey("UUID")) { + int[] arr = tag.getIntArray("UUID"); + tagUUID = new UUID((long) arr[0] << 32 | (arr[1] & 0xFFFFFFFFL), (long) arr[2] << 32 | (arr[3] & 0xFFFFFFFFL)); + } else if (tag.containsKey("UUIDMost")) { + tagUUID = new UUID(tag.getLong("UUIDMost"), tag.getLong("UUIDLeast")); + } else if (tag.containsKey("PersistentIDMSB")) { + tagUUID = new UUID(tag.getLong("PersistentIDMSB"), tag.getLong("PersistentIDLSB")); + } else { + return null; + } + if (uuid.equals(tagUUID)) { + return tag; + } + } + return null; + } + + protected void storeBiomes(BiomeStorage biomeStorage) { + this.biomeStorage = new BiomeStorage(biomeStorage.g, BukkitAdapter_1_16_4.getBiomeArray(biomeStorage).clone()); + } + + @Override + public BiomeType getBiomeType(int x, int y, int z) { + BiomeBase base = null; + if (y == -1) { + for (y = 0; y < FaweCache.IMP.WORLD_HEIGHT; y++) { + base = biomeStorage.getBiome(x >> 2, y >> 2, z >> 2); + if (base != null) break; + } + } else { + base = biomeStorage.getBiome(x >> 2, y >> 2, z >> 2); + } + return base != null ? BukkitAdapter.adapt(CraftBlock.biomeBaseToBiome(world.r().b(IRegistry.ay), base)) : null; + } + + protected void storeSection(int layer) { + blocks[layer] = update(layer, null).clone(); + } + + @Override + public BaseBlock getFullBlock(int x, int y, int z) { + BlockState state = BlockTypesCache.states[get(x, y, z)]; + return state.toBaseBlock(this, x, y, z); + } + + @Override + public BlockState getBlock(int x, int y, int z) { + return BlockTypesCache.states[get(x, y, z)]; + } + + @Override + public char get(int x, int y, int z) { + final int layer = y >> 4; + final int index = (y & 15) << 8 | z << 4 | x; + return blocks[layer][index]; + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/FAWEWorldNativeAccess_1_16.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/FAWEWorldNativeAccess_1_16.java new file mode 100644 index 000000000..b86902067 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/FAWEWorldNativeAccess_1_16.java @@ -0,0 +1,176 @@ +package com.boydti.fawe.bukkit.adapter.mc1_16_4; + +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.adapter.impl.FAWE_Spigot_v1_16_R2; +import com.sk89q.worldedit.bukkit.adapter.impl.FAWE_Spigot_v1_16_R3; +import com.sk89q.worldedit.internal.block.BlockStateIdAccess; +import com.sk89q.worldedit.internal.wna.WorldNativeAccess; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.util.SideEffect; +import com.sk89q.worldedit.util.SideEffectSet; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockStateHolder; +import net.minecraft.server.v1_16_R3.Block; +import net.minecraft.server.v1_16_R3.BlockPosition; +import net.minecraft.server.v1_16_R3.Chunk; +import net.minecraft.server.v1_16_R3.ChunkProviderServer; +import net.minecraft.server.v1_16_R3.EnumDirection; +import net.minecraft.server.v1_16_R3.IBlockData; +import net.minecraft.server.v1_16_R3.NBTBase; +import net.minecraft.server.v1_16_R3.NBTTagCompound; +import net.minecraft.server.v1_16_R3.PlayerChunk; +import net.minecraft.server.v1_16_R3.TileEntity; +import net.minecraft.server.v1_16_R3.World; +import org.bukkit.craftbukkit.v1_16_R3.CraftWorld; +import org.bukkit.craftbukkit.v1_16_R3.block.data.CraftBlockData; +import org.bukkit.event.block.BlockPhysicsEvent; + +import javax.annotation.Nullable; +import java.lang.ref.WeakReference; +import java.util.Objects; + +public class FAWEWorldNativeAccess_1_16 implements WorldNativeAccess { + private static final int UPDATE = 1; + private static final int NOTIFY = 2; + + private final FAWE_Spigot_v1_16_R3 adapter; + private final WeakReference world; + private SideEffectSet sideEffectSet; + + public FAWEWorldNativeAccess_1_16(FAWE_Spigot_v1_16_R3 adapter, WeakReference world) { + this.adapter = adapter; + this.world = world; + } + + private World getWorld() { + return Objects.requireNonNull(world.get(), "The reference to the world was lost"); + } + + @Override + public void setCurrentSideEffectSet(SideEffectSet sideEffectSet) { + this.sideEffectSet = sideEffectSet; + } + + @Override + public Chunk getChunk(int x, int z) { + return getWorld().getChunkAt(x, z); + } + + @Override + public IBlockData toNative(BlockState state) { + int stateId = BlockStateIdAccess.getBlockStateId(state); + return BlockStateIdAccess.isValidInternalId(stateId) + ? Block.getByCombinedId(stateId) + : ((CraftBlockData) BukkitAdapter.adapt(state)).getState(); + } + + @Override + public IBlockData getBlockState(Chunk chunk, BlockPosition position) { + return chunk.getType(position); + } + + @Nullable + @Override + public IBlockData setBlockState(Chunk chunk, BlockPosition position, IBlockData state) { + return chunk.setType(position, state, false); + } + + @Override + public IBlockData getValidBlockForPosition(IBlockData block, BlockPosition position) { + return Block.b(block, getWorld(), position); + } + + @Override + public BlockPosition getPosition(int x, int y, int z) { + return new BlockPosition(x, y, z); + } + + @Override + public void updateLightingForBlock(BlockPosition position) { + getWorld().getChunkProvider().getLightEngine().a(position); + } + + @Override + public boolean updateTileEntity(BlockPosition position, CompoundTag tag) { + // We will assume that the tile entity was created for us, + // though we do not do this on the other versions + TileEntity tileEntity = getWorld().getTileEntity(position); + if (tileEntity == null) { + return false; + } + NBTBase nativeTag = adapter.fromNative(tag); + tileEntity.load(tileEntity.getBlock(), (NBTTagCompound) nativeTag); + return true; + } + + @Override + public void notifyBlockUpdate(BlockPosition position, IBlockData oldState, IBlockData newState) { + getWorld().notify(position, oldState, newState, UPDATE | NOTIFY); + } + + @Override + public boolean isChunkTicking(Chunk chunk) { + return chunk.getState().isAtLeast(PlayerChunk.State.TICKING); + } + + @Override + public void markBlockChanged(BlockPosition position) { + ((ChunkProviderServer) getWorld().getChunkProvider()).flagDirty(position); + } + + private static final EnumDirection[] NEIGHBOUR_ORDER = { + EnumDirection.WEST, EnumDirection.EAST, + EnumDirection.DOWN, EnumDirection.UP, + EnumDirection.NORTH, EnumDirection.SOUTH + }; + + @Override + public void notifyNeighbors(BlockPosition pos, IBlockData oldState, IBlockData newState) { + World world = getWorld(); + if (sideEffectSet.shouldApply(SideEffect.EVENTS)) { + world.update(pos, oldState.getBlock()); + } else { + // When we don't want events, manually run the physics without them. + // Un-nest neighbour updating + for (EnumDirection direction : NEIGHBOUR_ORDER) { + BlockPosition shifted = pos.shift(direction); + world.getType(shifted).doPhysics(world, shifted, oldState.getBlock(), pos, false); + } + } + if (newState.isComplexRedstone()) { + world.updateAdjacentComparators(pos, newState.getBlock()); + } + } + + @Override + public void updateNeighbors(BlockPosition pos, IBlockData oldState, IBlockData newState, int recursionLimit) { + World world = getWorld(); + // a == updateNeighbors + // b == updateDiagonalNeighbors + oldState.b(world, pos, NOTIFY, recursionLimit); + if (sideEffectSet.shouldApply(SideEffect.EVENTS)) { + CraftWorld craftWorld = world.getWorld(); + if (craftWorld != null) { + BlockPhysicsEvent event = new BlockPhysicsEvent(craftWorld.getBlockAt(pos.getX(), pos.getY(), pos.getZ()), CraftBlockData.fromData(newState)); + world.getServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { + return; + } + } + } + newState.a(world, pos, NOTIFY, recursionLimit); + newState.b(world, pos, NOTIFY, recursionLimit); + } + + @Override + public void onBlockStateChange(BlockPosition pos, IBlockData oldState, IBlockData newState) { + getWorld().a(pos, oldState, newState); + } + + @Override + public > boolean setBlock(BlockVector3 position, B block, SideEffectSet sideEffects) throws WorldEditException { + return this.adapter.setBlock(this.getChunk(position.getBlockX() >> 4, position.getBlockZ() >> 4).bukkitChunk, position.getBlockX(), position.getBlockY(), position.getBlockZ(), block, sideEffectSet.shouldApply(SideEffect.LIGHTING)); + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/MapChunkUtil_1_16_4.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/MapChunkUtil_1_16_4.java new file mode 100644 index 000000000..37993c9fe --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/MapChunkUtil_1_16_4.java @@ -0,0 +1,28 @@ +package com.boydti.fawe.bukkit.adapter.mc1_16_4; + +import com.boydti.fawe.bukkit.adapter.MapChunkUtil; +import net.minecraft.server.v1_16_R3.PacketPlayOutMapChunk; + +public class MapChunkUtil_1_16_4 extends MapChunkUtil { + public MapChunkUtil_1_16_4() 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("f"); + fieldBlockEntities = PacketPlayOutMapChunk.class.getDeclaredField("g"); + fieldFull = PacketPlayOutMapChunk.class.getDeclaredField("h"); + 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_16_4/nbt/LazyCompoundTag_1_16_4.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/nbt/LazyCompoundTag_1_16_4.java new file mode 100644 index 000000000..7cc5cdc87 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/mc1_16_4/nbt/LazyCompoundTag_1_16_4.java @@ -0,0 +1,152 @@ +package com.boydti.fawe.bukkit.adapter.mc1_16_4.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_16_R3.NBTBase; +import net.minecraft.server.v1_16_R3.NBTNumber; +import net.minecraft.server.v1_16_R3.NBTTagCompound; +import net.minecraft.server.v1_16_R3.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_16_4 extends CompoundTag { + private final Supplier nmsTag; + + public LazyCompoundTag_1_16_4(Supplier tag) { + super(null); + this.nmsTag = tag; + } + + public LazyCompoundTag_1_16_4(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_16_4((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(); + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/AsyncChunk.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/AsyncChunk.java index e934d45ea..590668be6 100644 --- a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/AsyncChunk.java +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/AsyncChunk.java @@ -9,6 +9,7 @@ import org.bukkit.World; import org.bukkit.block.BlockState; import org.bukkit.block.data.BlockData; import org.bukkit.entity.Entity; +import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.plugin.Plugin; import org.jetbrains.annotations.NotNull; @@ -51,6 +52,11 @@ public class AsyncChunk implements Chunk { return z; } + @Override + public long getChunkKey() { + return Chunk.getChunkKey(getX(), getZ()); + } + @Override public AsyncWorld getWorld() { return world; @@ -140,7 +146,7 @@ public class AsyncChunk implements Chunk { @Override public boolean isSlimeChunk() { - return false; + return TaskManager.IMP.sync(() -> world.getChunkAt(x, z).isSlimeChunk()); } @Override @@ -170,17 +176,21 @@ public class AsyncChunk implements Chunk { @Override public long getInhabitedTime() { - return 0; //todo + return TaskManager.IMP.sync(() -> world.getChunkAt(x, z).getInhabitedTime()); } @Override public void setInhabitedTime(long ticks) { - //todo + world.getChunkAt(x, z).setInhabitedTime(ticks); } @Override public boolean contains(@NotNull BlockData block) { - //todo - return false; + return TaskManager.IMP.sync(() -> world.getChunkAt(x, z).contains(block)); + } + + @Override + public @NotNull PersistentDataContainer getPersistentDataContainer() { + return TaskManager.IMP.sync(() -> world.getChunkAt(x, z).getPersistentDataContainer()); } } diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_16_R3.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_16_R3.java new file mode 100644 index 000000000..dd03395a8 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_16_R3.java @@ -0,0 +1,420 @@ +/* + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.bukkit.adapter.impl; + +import com.bekvon.bukkit.residence.commands.material; +import com.boydti.fawe.FaweCache; +import com.boydti.fawe.beta.IChunkGet; +import com.boydti.fawe.beta.implementation.packet.ChunkPacket; +import com.boydti.fawe.bukkit.adapter.mc1_16_4.BlockMaterial_1_16_4; +import com.boydti.fawe.bukkit.adapter.mc1_16_4.BukkitAdapter_1_16_4; +import com.boydti.fawe.bukkit.adapter.mc1_16_4.BukkitGetBlocks_1_16_4; +import com.boydti.fawe.bukkit.adapter.mc1_16_4.FAWEWorldNativeAccess_1_16; +import com.boydti.fawe.bukkit.adapter.mc1_16_4.MapChunkUtil_1_16_4; +import com.boydti.fawe.bukkit.adapter.mc1_16_4.nbt.LazyCompoundTag_1_16_4; +import com.google.common.base.Preconditions; +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.jnbt.StringTag; +import com.sk89q.jnbt.Tag; +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.bukkit.adapter.impl.regen.Regen_v1_16_R3; +import com.sk89q.worldedit.entity.BaseEntity; +import com.sk89q.worldedit.entity.LazyBaseEntity; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.internal.wna.WorldNativeAccess; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.registry.state.Property; +import com.sk89q.worldedit.util.SideEffect; +import com.sk89q.worldedit.util.SideEffectSet; +import com.sk89q.worldedit.world.RegenOptions; +import com.sk89q.worldedit.world.biome.BiomeType; +import com.sk89q.worldedit.world.block.BaseBlock; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockStateHolder; +import com.sk89q.worldedit.world.block.BlockType; +import com.sk89q.worldedit.world.block.BlockTypes; +import com.sk89q.worldedit.world.block.BlockTypesCache; +import com.sk89q.worldedit.world.entity.EntityType; +import com.sk89q.worldedit.world.registry.BlockMaterial; +import net.minecraft.server.v1_16_R3.BiomeBase; +import net.minecraft.server.v1_16_R3.Block; +import net.minecraft.server.v1_16_R3.BlockPosition; +import net.minecraft.server.v1_16_R3.Chunk; +import net.minecraft.server.v1_16_R3.ChunkCoordIntPair; +import net.minecraft.server.v1_16_R3.ChunkSection; +import net.minecraft.server.v1_16_R3.Entity; +import net.minecraft.server.v1_16_R3.EntityPlayer; +import net.minecraft.server.v1_16_R3.EntityTypes; +import net.minecraft.server.v1_16_R3.IBlockData; +import net.minecraft.server.v1_16_R3.IRegistry; +import net.minecraft.server.v1_16_R3.ItemStack; +import net.minecraft.server.v1_16_R3.MinecraftKey; +import net.minecraft.server.v1_16_R3.MinecraftServer; +import net.minecraft.server.v1_16_R3.NBTBase; +import net.minecraft.server.v1_16_R3.NBTTagCompound; +import net.minecraft.server.v1_16_R3.NBTTagInt; +import net.minecraft.server.v1_16_R3.PacketPlayOutMapChunk; +import net.minecraft.server.v1_16_R3.PlayerChunk; +import net.minecraft.server.v1_16_R3.TileEntity; +import net.minecraft.server.v1_16_R3.World; +import net.minecraft.server.v1_16_R3.WorldServer; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.block.data.BlockData; +import org.bukkit.craftbukkit.v1_16_R3.CraftChunk; +import org.bukkit.craftbukkit.v1_16_R3.CraftWorld; +import org.bukkit.craftbukkit.v1_16_R3.block.CraftBlock; +import org.bukkit.craftbukkit.v1_16_R3.block.data.CraftBlockData; +import org.bukkit.craftbukkit.v1_16_R3.entity.CraftEntity; +import org.bukkit.craftbukkit.v1_16_R3.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftItemStack; +import org.bukkit.entity.Player; + +import javax.annotation.Nullable; +import java.lang.ref.WeakReference; +import java.util.Map; +import java.util.OptionalInt; +import java.util.Set; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import static org.slf4j.LoggerFactory.getLogger; + +public final class FAWE_Spigot_v1_16_R3 extends CachedBukkitAdapter implements IDelegateBukkitImplAdapter { + private final Spigot_v1_16_R3 parent; + private char[] ibdToStateOrdinal; + + // ------------------------------------------------------------------------ + // Code that may break between versions of Minecraft + // ------------------------------------------------------------------------ + + public FAWE_Spigot_v1_16_R3() throws NoSuchFieldException, NoSuchMethodException { + this.parent = new Spigot_v1_16_R3(); + } + + @Override + public BukkitImplAdapter getParent() { + return parent; + } + + private synchronized boolean init() { + if (ibdToStateOrdinal != null && ibdToStateOrdinal[1] != 0) { + return false; + } + ibdToStateOrdinal = new char[Block.REGISTRY_ID.a()]; // size + for (int i = 0; i < ibdToStateOrdinal.length; i++) { + BlockState state = BlockTypesCache.states[i]; + BlockMaterial_1_16_4 material = (BlockMaterial_1_16_4) state.getMaterial(); + int id = Block.REGISTRY_ID.getId(material.getState()); + ibdToStateOrdinal[id] = state.getOrdinalChar(); + } + return true; + } + + @Override + public BlockMaterial getMaterial(BlockType blockType) { + Block block = getBlock(blockType); + return new BlockMaterial_1_16_4(block); + } + + @Override + public BlockMaterial getMaterial(BlockState state) { + IBlockData bs = ((CraftBlockData) Bukkit.createBlockData(state.getAsString())).getState(); + return new BlockMaterial_1_16_4(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) { + Preconditions.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 Set getSupportedSideEffects() { + return SideEffectSet.defaults().getSideEffectsToApply(); + } + + public boolean setBlock(org.bukkit.Chunk chunk, int x, int y, int z, BlockStateHolder 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_16_4) state.getMaterial()).getState(); + ChunkSection[] sections = nmsChunk.getSections(); + int y4 = y >> 4; + ChunkSection section = sections[y4]; + + IBlockData existing; + if (section == null) { + existing = ((BlockMaterial_1_16_4) 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(tileEntity.getBlock(), 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; + } + + @Override + public WorldNativeAccess createWorldNativeAccess(org.bukkit.World world) { + return new FAWEWorldNativeAccess_1_16(this, + new WeakReference<>(((CraftWorld)world).getHandle())); + } + + @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) { + Preconditions.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); + + //add Id for AbstractChangeSet to work + CompoundTag natve = (CompoundTag) toNative(tag); + natve.getValue().put("Id", new StringTag(id)); + return natve; + }; + return new LazyBaseEntity(type, saveTag); + } else { + return null; + } + } + + @Override + public OptionalInt getInternalBlockStateId(BlockState state) { + BlockMaterial_1_16_4 material = (BlockMaterial_1_16_4) 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[adaptToChar(ibd)]; + } + + /** + * @deprecated + * Method unused. Use #adaptToChar(IBlockData). + */ + @Deprecated + public int adaptToInt(IBlockData ibd) { + synchronized (this) { + try { + int id = Block.REGISTRY_ID.getId(ibd); + return ibdToStateOrdinal[id]; + } catch (NullPointerException e) { + init(); + return adaptToInt(ibd); + } + } + } + + public char adaptToChar(IBlockData ibd) { + synchronized (this) { + try { + int id = Block.REGISTRY_ID.getId(ibd); + return ibdToStateOrdinal[id]; + } catch (NullPointerException e) { + init(); + return adaptToChar(ibd); + } catch (ArrayIndexOutOfBoundsException e1) { + getLogger(FAWE_Spigot_v1_16_R3.class) + .error("Attempted to convert {} with ID {} to char. ibdToStateOrdinal length: {}. Defaulting to air!", + ibd.getBlock(), Block.REGISTRY_ID.getId(ibd), ibdToStateOrdinal.length, e1); + return 0; + } + } + } + + @Override + public > BlockData adapt(B state) { + BlockMaterial_1_16_4 material = (BlockMaterial_1_16_4) state.getMaterial(); + return material.getCraftBlockData(); + } + + private MapChunkUtil_1_16_4 mapUtil = new MapChunkUtil_1_16_4(); + + @Override + public void sendFakeChunk(org.bukkit.World world, Player player, ChunkPacket packet) { + WorldServer nmsWorld = ((CraftWorld) world).getHandle(); + PlayerChunk map = BukkitAdapter_1_16_4.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_16_4) { + return ((LazyCompoundTag_1_16_4) foreign).get(); + } + return parent.fromNative(foreign); + } + + @Override + public boolean regenerate(org.bukkit.World bukkitWorld, Region region, Extent target, RegenOptions options) throws Exception { + return new Regen_v1_16_R3(bukkitWorld, region, target, options).regenerate(); + } + + @Override + public IChunkGet get(org.bukkit.World world, int chunkX, int chunkZ) { + return new BukkitGetBlocks_1_16_4(world, chunkX, chunkZ); + } + + @Override + public int getInternalBiomeId(BiomeType biome) { + BiomeBase base = CraftBlock.biomeToBiomeBase(MinecraftServer.getServer().getCustomRegistry().b(IRegistry.ay), BukkitAdapter.adapt(biome)); + return MinecraftServer.getServer().getCustomRegistry().b(IRegistry.ay).a(base); + } +} diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/regen/Regen_v1_16_R3.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/regen/Regen_v1_16_R3.java new file mode 100644 index 000000000..416e042aa --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/regen/Regen_v1_16_R3.java @@ -0,0 +1,579 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.regen; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.beta.IChunkCache; +import com.boydti.fawe.beta.IChunkGet; +import com.boydti.fawe.bukkit.adapter.mc1_16_4.BukkitGetBlocks_1_16_4; +import com.google.common.collect.ImmutableList; +import com.mojang.datafixers.util.Either; +import com.mojang.serialization.Codec; +import com.mojang.serialization.Dynamic; +import com.mojang.serialization.Lifecycle; +import com.sk89q.worldedit.bukkit.adapter.Regenerator; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.util.io.file.SafeFiles; +import com.sk89q.worldedit.world.RegenOptions; +import net.minecraft.server.v1_16_R3.Area; +import net.minecraft.server.v1_16_R3.AreaContextTransformed; +import net.minecraft.server.v1_16_R3.AreaFactory; +import net.minecraft.server.v1_16_R3.AreaTransformer8; +import net.minecraft.server.v1_16_R3.BiomeBase; +import net.minecraft.server.v1_16_R3.BiomeRegistry; +import net.minecraft.server.v1_16_R3.Chunk; +import net.minecraft.server.v1_16_R3.ChunkConverter; +import net.minecraft.server.v1_16_R3.ChunkCoordIntPair; +import net.minecraft.server.v1_16_R3.ChunkGenerator; +import net.minecraft.server.v1_16_R3.ChunkGeneratorAbstract; +import net.minecraft.server.v1_16_R3.ChunkProviderFlat; +import net.minecraft.server.v1_16_R3.ChunkProviderServer; +import net.minecraft.server.v1_16_R3.ChunkStatus; +import net.minecraft.server.v1_16_R3.Convertable; +import net.minecraft.server.v1_16_R3.DefinedStructureManager; +import net.minecraft.server.v1_16_R3.DynamicOpsNBT; +import net.minecraft.server.v1_16_R3.GenLayer; +import net.minecraft.server.v1_16_R3.GenLayers; +import net.minecraft.server.v1_16_R3.GeneratorSettingBase; +import net.minecraft.server.v1_16_R3.GeneratorSettings; +import net.minecraft.server.v1_16_R3.GeneratorSettingsFlat; +import net.minecraft.server.v1_16_R3.IChunkAccess; +import net.minecraft.server.v1_16_R3.IRegistry; +import net.minecraft.server.v1_16_R3.IRegistryCustom; +import net.minecraft.server.v1_16_R3.LightEngineThreaded; +import net.minecraft.server.v1_16_R3.LinearCongruentialGenerator; +import net.minecraft.server.v1_16_R3.MinecraftKey; +import net.minecraft.server.v1_16_R3.MinecraftServer; +import net.minecraft.server.v1_16_R3.NBTBase; +import net.minecraft.server.v1_16_R3.NBTTagCompound; +import net.minecraft.server.v1_16_R3.NoiseGeneratorPerlin; +import net.minecraft.server.v1_16_R3.ProtoChunk; +import net.minecraft.server.v1_16_R3.RegistryGeneration; +import net.minecraft.server.v1_16_R3.RegistryMaterials; +import net.minecraft.server.v1_16_R3.RegistryReadOps; +import net.minecraft.server.v1_16_R3.ResourceKey; +import net.minecraft.server.v1_16_R3.World; +import net.minecraft.server.v1_16_R3.WorldChunkManager; +import net.minecraft.server.v1_16_R3.WorldChunkManagerOverworld; +import net.minecraft.server.v1_16_R3.WorldDataServer; +import net.minecraft.server.v1_16_R3.WorldDimension; +import net.minecraft.server.v1_16_R3.WorldLoadListener; +import net.minecraft.server.v1_16_R3.WorldServer; +import net.minecraft.server.v1_16_R3.WorldSettings; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_16_R3.CraftServer; +import org.bukkit.craftbukkit.v1_16_R3.CraftWorld; +import org.bukkit.craftbukkit.v1_16_R3.generator.CustomChunkGenerator; +import org.bukkit.generator.BlockPopulator; + +import javax.annotation.Nullable; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BooleanSupplier; +import java.util.function.LongFunction; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +public class Regen_v1_16_R3 extends Regenerator { + + private static final Field serverWorldsField; + private static final Field worldPaperConfigField; + private static final Field flatBedrockField; + private static final Field generatorSettingBaseSupplierField; + private static final Field generatorSettingFlatField; + private static final Field delegateField; + private static final Field chunkProviderField; + + //list of chunk stati in correct order without FULL + private static final Map chunkStati = new LinkedHashMap<>(); + + static { + chunkStati.put(ChunkStatus.EMPTY, Regenerator.Concurrency.FULL); // radius -1, does nothing + chunkStati.put(ChunkStatus.STRUCTURE_STARTS, Regenerator.Concurrency.NONE); // uses unsynchronized maps + chunkStati.put(ChunkStatus.STRUCTURE_REFERENCES, Regenerator.Concurrency.FULL); // radius 8, but no writes to other chunks, only current chunk + chunkStati.put(ChunkStatus.BIOMES, Regenerator.Concurrency.FULL); // radius 0 + chunkStati.put(ChunkStatus.NOISE, Regenerator.Concurrency.RADIUS); // radius 8 + chunkStati.put(ChunkStatus.SURFACE, Regenerator.Concurrency.FULL); // radius 0 + chunkStati.put(ChunkStatus.CARVERS, Regenerator.Concurrency.NONE); // radius 0, but RADIUS and FULL change results + chunkStati.put(ChunkStatus.LIQUID_CARVERS, Regenerator.Concurrency.NONE); // radius 0, but RADIUS and FULL change results + chunkStati.put(ChunkStatus.FEATURES, Regenerator.Concurrency.NONE); // uses unsynchronized maps + chunkStati.put(ChunkStatus.LIGHT, Regenerator.Concurrency.FULL); // radius 1, but no writes to other chunks, only current chunk + chunkStati.put(ChunkStatus.SPAWN, Regenerator.Concurrency.FULL); // radius 0 + chunkStati.put(ChunkStatus.HEIGHTMAPS, Regenerator.Concurrency.FULL); // radius 0 + + try { + serverWorldsField = CraftServer.class.getDeclaredField("worlds"); + serverWorldsField.setAccessible(true); + + Field tmpPaperConfigField = null; + Field tmpFlatBedrockField = null; + try { //only present on paper + tmpPaperConfigField = World.class.getDeclaredField("paperConfig"); + tmpPaperConfigField.setAccessible(true); + + tmpFlatBedrockField = tmpPaperConfigField.getType().getDeclaredField("generateFlatBedrock"); + tmpFlatBedrockField.setAccessible(true); + } catch (Exception e) { + tmpPaperConfigField = null; + tmpFlatBedrockField = null; + } + worldPaperConfigField = tmpPaperConfigField; + flatBedrockField = tmpFlatBedrockField; + + generatorSettingBaseSupplierField = ChunkGeneratorAbstract.class.getDeclaredField("h"); + generatorSettingBaseSupplierField.setAccessible(true); + + generatorSettingFlatField = ChunkProviderFlat.class.getDeclaredField("e"); + generatorSettingFlatField.setAccessible(true); + + delegateField = CustomChunkGenerator.class.getDeclaredField("delegate"); + delegateField.setAccessible(true); + + chunkProviderField = WorldServer.class.getDeclaredField("chunkProvider"); + chunkProviderField.setAccessible(true); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + //runtime + private WorldServer originalNMSWorld; + private ChunkProviderServer originalChunkProvider; + private WorldServer freshNMSWorld; + private ChunkProviderServer freshChunkProvider; + private Convertable.ConversionSession session; + private DefinedStructureManager structureManager; + private LightEngineThreaded lightEngine; + private ChunkGenerator generator; + + private Path tempDir; + + private boolean generateFlatBedrock = false; + + public Regen_v1_16_R3(org.bukkit.World originalBukkitWorld, Region region, Extent target, RegenOptions options) { + super(originalBukkitWorld, region, target, options); + } + + @Override + protected boolean prepare() { + this.originalNMSWorld = ((CraftWorld) originalBukkitWorld).getHandle(); + originalChunkProvider = originalNMSWorld.getChunkProvider(); + if (!(originalChunkProvider instanceof ChunkProviderServer)) { + return false; + } + + //flat bedrock? (only on paper) + try { + generateFlatBedrock = flatBedrockField.getBoolean(worldPaperConfigField.get(originalNMSWorld)); + } catch (Exception ignored) { + } + + seed = options.getSeed().orElse(originalNMSWorld.getSeed()); + chunkStati.forEach((s, c) -> super.chunkStati.put(new ChunkStatusWrap(s), c)); + + return true; + } + + @Override + protected boolean initNewWorld() throws Exception { + //world folder + tempDir = java.nio.file.Files.createTempDirectory("WorldEditWorldGen"); + + //prepare for world init (see upstream implementation for reference) + org.bukkit.World.Environment env = originalBukkitWorld.getEnvironment(); + org.bukkit.generator.ChunkGenerator gen = originalBukkitWorld.getGenerator(); + Convertable convertable = Convertable.a(tempDir); + ResourceKey worldDimKey = getWorldDimKey(env); + session = convertable.c("worldeditregentempworld", worldDimKey); + WorldDataServer originalWorldData = originalNMSWorld.worldDataServer; + + MinecraftServer server = originalNMSWorld.getServer().getServer(); + WorldDataServer levelProperties = (WorldDataServer) server.getSaveData(); + RegistryReadOps nbtRegOps = RegistryReadOps.a(DynamicOpsNBT.a, server.dataPackResources.h(), IRegistryCustom.b()); + GeneratorSettings newOpts = GeneratorSettings.a.encodeStart(nbtRegOps, levelProperties.getGeneratorSettings()).flatMap(tag -> GeneratorSettings.a.parse(this.recursivelySetSeed(new Dynamic<>(nbtRegOps, tag), seed, new HashSet<>()))).result().orElseThrow(() -> new IllegalStateException("Unable to map GeneratorOptions")); + WorldSettings newWorldSettings = new WorldSettings("worldeditregentempworld", originalWorldData.b.getGameType(), originalWorldData.b.hardcore, originalWorldData.b.getDifficulty(), originalWorldData.b.e(), originalWorldData.b.getGameRules(), originalWorldData.b.g()); + WorldDataServer newWorldData = new WorldDataServer(newWorldSettings, newOpts, Lifecycle.stable()); + + //init world + freshNMSWorld = Fawe.get().getQueueHandler().sync((Supplier) () -> new WorldServer(server, server.executorService, session, newWorldData, originalNMSWorld.getDimensionKey(), originalNMSWorld.getDimensionManager(), new RegenNoOpWorldLoadListener(), ((WorldDimension) newOpts.d().a(worldDimKey)).c(), originalNMSWorld.isDebugWorld(), seed, ImmutableList.of(), false, env, gen) { + @Override + public void doTick(BooleanSupplier booleansupplier) { //no ticking + } + + private final BiomeBase singleBiome = options.hasBiomeType() ? RegistryGeneration.WORLDGEN_BIOME.get(MinecraftKey.a(options.getBiomeType().getId())) : null; + + @Override + public BiomeBase a(int i, int j, int k) { + if (options.hasBiomeType()) { + return singleBiome; + } + return this.getChunkProvider().getChunkGenerator().getWorldChunkManager().getBiome(i, j, k); + } + }).get(); + freshNMSWorld.savingDisabled = true; + removeWorldFromWorldsMap(); + newWorldData.checkName(originalNMSWorld.worldDataServer.getName()); //rename to original world name + + freshChunkProvider = new ChunkProviderServer(freshNMSWorld, session, server.getDataFixer(), server.getDefinedStructureManager(), server.executorService, originalChunkProvider.chunkGenerator, freshNMSWorld.spigotConfig.viewDistance, server.isSyncChunkWrites(), new RegenNoOpWorldLoadListener(), () -> server.E().getWorldPersistentData()) { + // redirect to our protoChunks list + @Override + public IChunkAccess getChunkAt(int x, int z, ChunkStatus chunkstatus, boolean flag) { + return getProtoChunkAt(x, z); + } + }; + chunkProviderField.set(freshNMSWorld, freshChunkProvider); + + //generator + if (originalChunkProvider.getChunkGenerator() instanceof ChunkProviderFlat) { + GeneratorSettingsFlat generatorSettingFlat = (GeneratorSettingsFlat) generatorSettingFlatField.get(originalChunkProvider.getChunkGenerator()); + generator = new ChunkProviderFlat(generatorSettingFlat); + } else if (originalChunkProvider.getChunkGenerator() instanceof ChunkGeneratorAbstract) { + Supplier generatorSettingBaseSupplier = (Supplier) generatorSettingBaseSupplierField.get(originalChunkProvider.getChunkGenerator()); + WorldChunkManager chunkManager = originalChunkProvider.getChunkGenerator().getWorldChunkManager(); + if (chunkManager instanceof WorldChunkManagerOverworld) { + chunkManager = fastOverWorldChunkManager(chunkManager); + } + generator = new ChunkGeneratorAbstract(chunkManager, seed, generatorSettingBaseSupplier); + } else if (originalChunkProvider.getChunkGenerator() instanceof CustomChunkGenerator) { + ChunkGenerator delegate = (ChunkGenerator) delegateField.get(originalChunkProvider.getChunkGenerator()); + generator = delegate; + } else { + System.out.println("Unsupported generator type " + originalChunkProvider.getChunkGenerator().getClass().getName()); + return false; + } + if (originalNMSWorld.generator != null) { + // wrap custom world generator + generator = new CustomChunkGenerator(freshNMSWorld, generator, originalNMSWorld.generator); + generateConcurrent = originalNMSWorld.generator.isParallelCapable(); + } + + //lets start then + structureManager = server.getDefinedStructureManager(); + lightEngine = freshChunkProvider.getLightEngine(); + + return true; + } + + @Override + protected void cleanup() { + try { + session.close(); + } catch (Exception e) { + } + + //shutdown chunk provider + try { + Fawe.get().getQueueHandler().sync(() -> { + try { + freshChunkProvider.close(false); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } catch (Exception e) { + } + + //remove world from server + try { + Fawe.get().getQueueHandler().sync(() -> { + removeWorldFromWorldsMap(); + }); + } catch (Exception e) { + } + + //delete directory + try { + SafeFiles.tryHardToDeleteDir(tempDir); + } catch (Exception e) { + } + } + + @Override + protected ProtoChunk createProtoChunk(int x, int z) { + return new ProtoChunk(new ChunkCoordIntPair(x, z), ChunkConverter.a) { + public boolean generateFlatBedrock() { + return generateFlatBedrock; + } + }; + } + + @Override + protected Chunk createChunk(ProtoChunk protoChunk) { + return new Chunk(freshNMSWorld, protoChunk); + } + + @Override + protected ChunkStatusWrap getFullChunkStatus() { + return new ChunkStatusWrap(ChunkStatus.FULL); + } + + @Override + protected List getBlockPopulators() { + return originalNMSWorld.getWorld().getPopulators(); + } + + @Override + protected void populate(Chunk chunk, Random random, BlockPopulator pop) { + pop.populate(freshNMSWorld.getWorld(), random, chunk.bukkitChunk); + } + + @Override + protected IChunkCache initSourceQueueCache() { + return (chunkX, chunkZ) -> new BukkitGetBlocks_1_16_4(freshNMSWorld, chunkX, chunkZ) { + @Override + public Chunk ensureLoaded(World nmsWorld, int x, int z) { + return getChunkAt(x, z); + } + }; + } + + protected class ChunkStatusWrap extends Regenerator.ChunkStatusWrapper { + + private final ChunkStatus chunkStatus; + + public ChunkStatusWrap(ChunkStatus chunkStatus) { + this.chunkStatus = chunkStatus; + } + + @Override + public int requiredNeigborChunkRadius() { + return chunkStatus.f(); + } + + @Override + public String name() { + return chunkStatus.d(); + } + + @Override + public void processChunk(Long xz, List accessibleChunks) { + chunkStatus.a(freshNMSWorld, + generator, + structureManager, + lightEngine, + c -> CompletableFuture.completedFuture(Either.left(c)), + accessibleChunks); + } + } + + //util + private void removeWorldFromWorldsMap() { + Fawe.get().getQueueHandler().sync(() -> { + try { + Map map = (Map) serverWorldsField.get(Bukkit.getServer()); + map.remove("worldeditregentempworld"); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + }); + } + + private ResourceKey getWorldDimKey(org.bukkit.World.Environment env) { + switch (env) { + case NETHER: + return WorldDimension.THE_NETHER; + case THE_END: + return WorldDimension.THE_END; + case NORMAL: + default: + return WorldDimension.OVERWORLD; + } + } + + private Dynamic recursivelySetSeed(Dynamic dynamic, long seed, Set> seen) { + return !seen.add(dynamic) ? dynamic : dynamic.updateMapValues((pair) -> { + if (((Dynamic) pair.getFirst()).asString("").equals("seed")) { + return pair.mapSecond((v) -> { + return v.createLong(seed); + }); + } else { + return ((Dynamic) pair.getSecond()).getValue() instanceof NBTTagCompound ? pair.mapSecond((v) -> { + return this.recursivelySetSeed((Dynamic) v, seed, seen); + }) : pair; + + } + }); + } + + private WorldChunkManager fastOverWorldChunkManager(WorldChunkManager chunkManager) throws Exception { + Field legacyBiomeInitLayerField = WorldChunkManagerOverworld.class.getDeclaredField("i"); + legacyBiomeInitLayerField.setAccessible(true); + Field largeBiomesField = WorldChunkManagerOverworld.class.getDeclaredField("j"); + largeBiomesField.setAccessible(true); + Field biomeRegistryField = WorldChunkManagerOverworld.class.getDeclaredField("k"); + biomeRegistryField.setAccessible(true); + Field areaLazyField = GenLayer.class.getDeclaredField("b"); + areaLazyField.setAccessible(true); + Method initAreaFactoryMethod = GenLayers.class.getDeclaredMethod("a", boolean.class, int.class, int.class, LongFunction.class); + initAreaFactoryMethod.setAccessible(true); + + //init new WorldChunkManagerOverworld + boolean legacyBiomeInitLayer = legacyBiomeInitLayerField.getBoolean(chunkManager); + boolean largebiomes = largeBiomesField.getBoolean(chunkManager); + IRegistry biomeRegistrynms = (IRegistry) biomeRegistryField.get(chunkManager); + IRegistry biomeRegistry; + if (options.hasBiomeType()) { + BiomeBase biome = RegistryGeneration.WORLDGEN_BIOME.get(MinecraftKey.a(options.getBiomeType().getId())); + biomeRegistry = new RegistryMaterials<>(ResourceKey.a(new MinecraftKey("fawe_biomes")), Lifecycle.experimental()); + ((RegistryMaterials) biomeRegistry).a(0, RegistryGeneration.WORLDGEN_BIOME.c(biome).get(), biome, Lifecycle.experimental()); + } else { + biomeRegistry = biomeRegistrynms; + } + chunkManager = new FastWorldChunkManagerOverworld(seed, legacyBiomeInitLayer, largebiomes, biomeRegistry); + + //replace genLayer + AreaFactory factory = (AreaFactory) initAreaFactoryMethod.invoke(null, legacyBiomeInitLayer, largebiomes ? 6 : 4, 4, (LongFunction) (l -> new FastWorldGenContextArea(seed, l))); + ((FastWorldChunkManagerOverworld) chunkManager).genLayer = new FastGenLayer(factory); + + return chunkManager; + } + + private static class FastWorldChunkManagerOverworld extends WorldChunkManager { + + private GenLayer genLayer; + private final IRegistry k; + private final boolean isSingleRegistry; + + public FastWorldChunkManagerOverworld(long seed, boolean legacyBiomeInitLayer, boolean largeBiomes, IRegistry biomeRegistry) { + super(biomeRegistry.g().collect(Collectors.toList())); + this.k = biomeRegistry; + this.isSingleRegistry = biomeRegistry.d().size() == 1; + this.genLayer = GenLayers.a(seed, legacyBiomeInitLayer, largeBiomes ? 6 : 4, 4); + } + + @Override + protected Codec a() { + return WorldChunkManagerOverworld.e; + } + + @Override + public BiomeBase getBiome(int i, int i1, int i2) { + if (this.isSingleRegistry) { + return this.k.fromId(0); + } + return this.genLayer.a(this.k, i, i2); + } + } + + + private static class FastWorldGenContextArea implements AreaContextTransformed { + + private final ConcurrentHashMap sharedAreaMap = new ConcurrentHashMap<>(); + private final NoiseGeneratorPerlin perlinNoise; + private final long magicrandom; + private final ConcurrentHashMap map = new ConcurrentHashMap<>(); //needed for multithreaded generation + + public FastWorldGenContextArea(long seed, long lconst) { + this.magicrandom = mix(seed, lconst); + this.perlinNoise = new NoiseGeneratorPerlin(new Random(seed)); + } + + @Override + public FastAreaLazy a(AreaTransformer8 var0) { + return new FastAreaLazy(sharedAreaMap, var0); + } + + @Override + public void a(long x, long z) { + long l = this.magicrandom; + l = LinearCongruentialGenerator.a(l, x); + l = LinearCongruentialGenerator.a(l, z); + l = LinearCongruentialGenerator.a(l, x); + l = LinearCongruentialGenerator.a(l, z); + this.map.put(Thread.currentThread().getId(), l); + } + + @Override + public int a(int y) { + long tid = Thread.currentThread().getId(); + long e = this.map.computeIfAbsent(tid, i -> 0L); + int mod = (int) Math.floorMod(e >> 24L, (long) y); + this.map.put(tid, LinearCongruentialGenerator.a(e, this.magicrandom)); + return mod; + } + + @Override + public NoiseGeneratorPerlin b() { + return this.perlinNoise; + } + + private static long mix(long seed, long lconst) { + long l1 = lconst; + l1 = LinearCongruentialGenerator.a(l1, lconst); + l1 = LinearCongruentialGenerator.a(l1, lconst); + l1 = LinearCongruentialGenerator.a(l1, lconst); + long l2 = seed; + l2 = LinearCongruentialGenerator.a(l2, l1); + l2 = LinearCongruentialGenerator.a(l2, l1); + l2 = LinearCongruentialGenerator.a(l2, l1); + return l2; + } + } + + private static class FastGenLayer extends GenLayer { + + private final FastAreaLazy areaLazy; + + public FastGenLayer(AreaFactory factory) throws Exception { + super(() -> null); + this.areaLazy = factory.make(); + } + + @Override + public BiomeBase a(IRegistry registry, int x, int z) { + ResourceKey key = BiomeRegistry.a(this.areaLazy.a(x, z)); + if (key == null) + return registry.a(BiomeRegistry.a(0)); + BiomeBase biome = registry.a(key); + if (biome == null) + return registry.a(BiomeRegistry.a(0)); + return biome; + } + } + + private static class FastAreaLazy implements Area { + + private final AreaTransformer8 transformer; + //ConcurrentHashMap is 50% faster that Long2IntLinkedOpenHashMap in a syncronized context + //using a map for each thread worsens the performance significantly due to cache misses (factor 5) + private final ConcurrentHashMap sharedMap; + + public FastAreaLazy(ConcurrentHashMap sharedMap, AreaTransformer8 transformer) { + this.sharedMap = sharedMap; + this.transformer = transformer; + } + + @Override + public int a(int x, int z) { + long zx = ChunkCoordIntPair.pair(x, z); + return this.sharedMap.computeIfAbsent(zx, i -> this.transformer.apply(x, z)); + } + } + + private static class RegenNoOpWorldLoadListener implements WorldLoadListener { + + private RegenNoOpWorldLoadListener() { + } + + @Override + public void a(ChunkCoordIntPair chunkCoordIntPair) { + } + + @Override + public void a(ChunkCoordIntPair chunkCoordIntPair, @Nullable ChunkStatus chunkStatus) { + } + + @Override + public void b() { + } + } +}