From 3bf2ccdebcfc5c06770286c0e2d75fc9c5b9c152 Mon Sep 17 00:00:00 2001 From: Jesse Boyd Date: Wed, 24 Apr 2019 02:48:42 +1000 Subject: [PATCH] wip 1.14 --- .../fawe/bukkit/v1_14/BukkitChunk_1_14.java | 618 ++++++++++++ .../fawe/bukkit/v1_14/BukkitQueue_1_14.java | 938 ++++++++++++++++++ .../v1_14/adapter/BlockMaterial_1_14.java | 151 +++ .../bukkit/v1_14/adapter/Spigot_v1_14_R1.java | 611 ++++++++++++ 4 files changed, 2318 insertions(+) create mode 100644 worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v1_14/BukkitChunk_1_14.java create mode 100644 worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v1_14/BukkitQueue_1_14.java create mode 100644 worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v1_14/adapter/BlockMaterial_1_14.java create mode 100644 worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v1_14/adapter/Spigot_v1_14_R1.java diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v1_14/BukkitChunk_1_14.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v1_14/BukkitChunk_1_14.java new file mode 100644 index 000000000..be2ba6723 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v1_14/BukkitChunk_1_14.java @@ -0,0 +1,618 @@ +package com.boydti.fawe.bukkit.v1_14; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.bukkit.v0.BukkitQueue_0; +import com.boydti.fawe.bukkit.v1_14.adapter.BlockMaterial_1_14; +import com.boydti.fawe.bukkit.v1_14.adapter.Spigot_v1_14_R1; +import com.boydti.fawe.config.Settings; +import com.boydti.fawe.example.IntFaweChunk; +import com.boydti.fawe.jnbt.anvil.BitArray4096; +import com.boydti.fawe.object.FaweChunk; +import com.boydti.fawe.object.FaweQueue; +import com.boydti.fawe.util.MainUtil; +import com.boydti.fawe.util.MathMan; +import com.boydti.fawe.util.ReflectionUtils; +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.jnbt.ListTag; +import com.sk89q.jnbt.LongTag; +import com.sk89q.jnbt.StringTag; +import com.sk89q.jnbt.Tag; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.internal.Constants; +import com.sk89q.worldedit.world.biome.BiomeType; +import com.sk89q.worldedit.world.block.BlockID; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockTypes; +import net.minecraft.server.v1_14_R1.BiomeBase; +import net.minecraft.server.v1_14_R1.Block; +import net.minecraft.server.v1_14_R1.BlockPosition; +import net.minecraft.server.v1_14_R1.Blocks; +import net.minecraft.server.v1_14_R1.ChunkSection; +import net.minecraft.server.v1_14_R1.DataBits; +import net.minecraft.server.v1_14_R1.DataPalette; +import net.minecraft.server.v1_14_R1.DataPaletteBlock; +import net.minecraft.server.v1_14_R1.DataPaletteHash; +import net.minecraft.server.v1_14_R1.DataPaletteLinear; +import net.minecraft.server.v1_14_R1.Entity; +import net.minecraft.server.v1_14_R1.EntityPlayer; +import net.minecraft.server.v1_14_R1.EntityTypes; +import net.minecraft.server.v1_14_R1.GameProfileSerializer; +import net.minecraft.server.v1_14_R1.IBlockData; +import net.minecraft.server.v1_14_R1.MinecraftKey; +import net.minecraft.server.v1_14_R1.NBTTagCompound; +import net.minecraft.server.v1_14_R1.NBTTagInt; +import net.minecraft.server.v1_14_R1.NibbleArray; +import net.minecraft.server.v1_14_R1.RegistryID; +import net.minecraft.server.v1_14_R1.TileEntity; +import org.bukkit.Chunk; +import org.bukkit.World; +import org.bukkit.block.Biome; +import org.bukkit.craftbukkit.v1_14_R1.CraftChunk; +import org.bukkit.craftbukkit.v1_14_R1.block.CraftBlock; +import org.bukkit.event.entity.CreatureSpawnEvent; + +import java.lang.reflect.InvocationTargetException; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import static com.boydti.fawe.bukkit.v0.BukkitQueue_0.getAdapter; +import static com.boydti.fawe.bukkit.v1_14.BukkitQueue_1_14.fieldRegistryb; +import static com.boydti.fawe.bukkit.v1_14.BukkitQueue_1_14.fieldRegistryc; +import static com.boydti.fawe.bukkit.v1_14.BukkitQueue_1_14.fieldRegistryd; +import static com.boydti.fawe.bukkit.v1_14.BukkitQueue_1_14.fieldRegistrye; +import static com.boydti.fawe.bukkit.v1_14.BukkitQueue_1_14.fieldRegistryf; + +public class BukkitChunk_1_14 extends IntFaweChunk { + + public ChunkSection[] sectionPalettes; + + private static final IBlockData AIR = ((BlockMaterial_1_14) BlockTypes.AIR.getMaterial()).getState(); + + /** + * A FaweSections object represents a chunk and the blocks that you wish to change in it. + * + * @param parent + * @param x + * @param z + */ + public BukkitChunk_1_14(FaweQueue parent, int x, int z) { + super(parent, x, z); + } + + public BukkitChunk_1_14(FaweQueue parent, int x, int z, int[][] ids, short[] count, short[] air) { + super(parent, x, z, ids, count, air); + } + + public void storeBiomes(BiomeBase[] biomes) { + if (biomes != null) { + if (this.biomes == null) { + this.biomes = new BiomeType[256]; + } + for (int i = 0; i < 256; i++) { + this.biomes[i] = BukkitAdapter.adapt(CraftBlock.biomeBaseToBiome(biomes[i])); + } + } + } + + @Override + public int[][] getCombinedIdArrays() { + if (this.sectionPalettes != null) { + for (int i = 0; i < setBlocks.length; i++) { + getIdArray(i); + } + } + return this.setBlocks; + } + + @Override + public int[] getIdArray(int layer) { + if (this.setBlocks[layer] == null && this.sectionPalettes != null) { + ChunkSection section = this.sectionPalettes[layer]; + int[] idsArray = this.setBlocks[layer]; + if (section != null && idsArray == null) { + this.setBlocks[layer] = idsArray = new int[4096]; + if (!section.c()) { + try { + DataPaletteBlock blocks = section.getBlocks(); + DataBits bits = (DataBits) BukkitQueue_1_14.fieldBits.get(blocks); + DataPalette palette = (DataPalette) BukkitQueue_1_14.fieldPalette.get(blocks); + + long[] raw = bits.a(); + int bitsPerEntry = bits.c(); + + new BitArray4096(raw, bitsPerEntry).toRaw(idsArray); + IBlockData defaultBlock = (IBlockData) BukkitQueue_1_14.fieldDefaultBlock.get(blocks); + // TODO optimize away palette.a + for (int i = 0; i < 4096; i++) { + IBlockData ibd = palette.a(idsArray[i]); + if (ibd == null) { + ibd = defaultBlock; + } + int ordinal = ((Spigot_v1_14_R1) getAdapter()).adaptToInt(ibd); + idsArray[i] = BlockTypes.states[ordinal].getInternalId(); + } + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + } + } + return this.setBlocks[layer]; + } + + public boolean storeTile(TileEntity tile, BlockPosition pos) { + CompoundTag nativeTag = getParent().getTag(tile); + setTile(pos.getX() & 15, pos.getY(), pos.getZ() & 15, nativeTag); + return true; + } + + public boolean storeEntity(Entity ent) throws InvocationTargetException, IllegalAccessException { + if (ent instanceof EntityPlayer || BukkitQueue_0.getAdapter() == null) { + return false; + } + EntityTypes type = ent.getEntityType(); + MinecraftKey id = EntityTypes.getName(type); + if (id != null) { + NBTTagCompound tag = new NBTTagCompound(); + ent.save(tag); // readEntityIntoTag + CompoundTag nativeTag = (CompoundTag) BukkitQueue_0.toNative(tag); + Map map = ReflectionUtils.getMap(nativeTag.getValue()); + map.put("Id", new StringTag(id.toString())); + setEntity(nativeTag); + return true; + } else { + return false; + } + } + + public boolean storeSection(ChunkSection section, int layer) throws IllegalAccessException { + if (sectionPalettes == null) { + // TODO optimize don't copy light + sectionPalettes = new ChunkSection[16]; + } + sectionPalettes[layer] = section; + return true; + } + + public ChunkSection copy(ChunkSection current) throws IllegalAccessException, InvocationTargetException, NoSuchFieldException { + ChunkSection newSection = new ChunkSection(current.getYPosition()); + + // Copy counters + Object nonEmptyBlockCount = BukkitQueue_1_14.fieldNonEmptyBlockCount.get(current); + BukkitQueue_1_14.fieldNonEmptyBlockCount.set(newSection, nonEmptyBlockCount); + + Object tickingBlockCount = BukkitQueue_1_14.fieldTickingBlockCount.get(current); + BukkitQueue_1_14.fieldTickingBlockCount.set(newSection, tickingBlockCount); + + Object liquidCount = BukkitQueue_1_14.fieldLiquidCount.get(current); + BukkitQueue_1_14.fieldLiquidCount.set(newSection, liquidCount); + + // Copy blocks + DataPaletteBlock blocks = current.getBlocks(); + DataPaletteBlock blocksCopy = copy(blocks); + BukkitQueue_1_14.fieldSection.set(newSection, blocksCopy); + + return newSection; + } + + public DataPaletteBlock copy(DataPaletteBlock current) throws IllegalAccessException, InvocationTargetException, NoSuchFieldException { + // Clone palette + DataPalette currentPalette = (DataPalette) BukkitQueue_1_14.fieldPalette.get(current); + DataPaletteBlock paletteBlock = newDataPaletteBlock(); + int size = BukkitQueue_1_14.fieldSize.getInt(current); + + DataPalette newPalette = currentPalette; + if (currentPalette instanceof DataPaletteHash) { + // TODO optimize resize + newPalette = new DataPaletteHash<>(Block.REGISTRY_ID, size, paletteBlock, GameProfileSerializer::d, GameProfileSerializer::a); + RegistryID currReg = (RegistryID) BukkitQueue_1_14.fieldHashBlocks.get(currentPalette); + RegistryID newReg = (RegistryID) BukkitQueue_1_14.fieldHashBlocks.get(newPalette); + int arrLen = 1 << size; + System.arraycopy(fieldRegistryb.get(currReg), 0, fieldRegistryb.get(newReg), 0, arrLen); + System.arraycopy(fieldRegistryc.get(currReg), 0, fieldRegistryc.get(newReg), 0, arrLen); + System.arraycopy(fieldRegistryd.get(currReg), 0, fieldRegistryd.get(newReg), 0, arrLen); + fieldRegistrye.set(newReg, fieldRegistrye.get(currReg)); + fieldRegistryf.set(newReg, fieldRegistryf.get(currReg)); + } else if (currentPalette instanceof DataPaletteLinear) { + // TODO optimize resize + newPalette = new DataPaletteLinear<>(Block.REGISTRY_ID, size, paletteBlock, GameProfileSerializer::d); + Object[] currArray = ((Object[]) BukkitQueue_1_14.fieldLinearBlocks.get(currentPalette)); + Object[] newArray = ((Object[]) BukkitQueue_1_14.fieldLinearBlocks.get(newPalette)); + BukkitQueue_1_14.fieldLinearIndex.set(newPalette, BukkitQueue_1_14.fieldLinearIndex.get(currentPalette)); + for (int i = 0; i < newArray.length; i++) newArray[i] = currArray[i]; + } + + BukkitQueue_1_14.fieldPalette.set(paletteBlock, newPalette); + // Clone size + BukkitQueue_1_14.fieldSize.set(paletteBlock, size); + // Clone palette + DataBits currentBits = (DataBits) BukkitQueue_1_14.fieldBits.get(current); + DataBits newBits = new DataBits(currentBits.c(), currentBits.b(), currentBits.a().clone()); + BukkitQueue_1_14.fieldBits.set(paletteBlock, newBits); + + // TODO copy only if different + Object defaultBlock = BukkitQueue_1_14.fieldDefaultBlock.get(current); + if (defaultBlock != AIR) { + ReflectionUtils.setFailsafeFieldValue(BukkitQueue_1_14.fieldDefaultBlock, paletteBlock, BukkitQueue_1_14.fieldDefaultBlock.get(current)); + } + + return paletteBlock; + } + + @Override + public IntFaweChunk copy(boolean shallow) { + BukkitChunk_1_14 copy; + if (shallow) { + copy = new BukkitChunk_1_14(getParent(), getX(), getZ(), setBlocks, count, air); + copy.biomes = biomes; + copy.chunk = chunk; + } else { + copy = new BukkitChunk_1_14(getParent(), getX(), getZ(), (int[][]) MainUtil.copyNd(setBlocks), count.clone(), air.clone()); + copy.biomes = biomes != null ? biomes.clone() : null; + copy.chunk = chunk; + } + if (sectionPalettes != null) { + copy.sectionPalettes = new ChunkSection[16]; + try { + for (int i = 0; i < sectionPalettes.length; i++) { + ChunkSection current = sectionPalettes[i]; + if (current == null) { + continue; + } + copy.sectionPalettes[i] = copy(current); + } + } catch (Throwable e) { + MainUtil.handleError(e); + } + } + return copy; + } + + private DataPaletteBlock newDataPaletteBlock() { + return new DataPaletteBlock<>(ChunkSection.GLOBAL_PALETTE, Block.REGISTRY_ID, GameProfileSerializer::d, GameProfileSerializer::a, Blocks.AIR.getBlockData()); + } + + @Override + public Chunk getNewChunk() { + return ((BukkitQueue_1_14) getParent()).getWorld().getChunkAt(getX(), getZ()); + } + + public void optimize() { + if (sectionPalettes != null) { + return; + } + int[][] arrays = getCombinedIdArrays(); + for (int layer = 0; layer < 16; layer++) { + if (getCount(layer) > 0) { + if (sectionPalettes == null) { + sectionPalettes = new ChunkSection[16]; + } + int[] array = arrays[layer]; + sectionPalettes[layer] = BukkitQueue_1_14.newChunkSection(layer, getParent().hasSky(), array); + } + } + } + + @Override + public void start() { + getChunk().load(true); + } + + private void removeEntity(Entity entity) { + entity.die(); + entity.valid = false; + } + + @Override + public FaweChunk call() { + Spigot_v1_14_R1 adapter = (Spigot_v1_14_R1) BukkitQueue_0.getAdapter(); + try { + BukkitChunk_1_14 copy = getParent().getChangeTask() != null ? new BukkitChunk_1_14(getParent(), getX(), getZ()) : null; + final Chunk chunk = this.getChunk(); + final World world = chunk.getWorld(); + Settings settings = getParent().getSettings(); + int bx = this.getX() << 4; + int bz = this.getZ() << 4; + final boolean flag = world.getEnvironment() == World.Environment.NORMAL; + net.minecraft.server.v1_14_R1.Chunk nmsChunk = ((CraftChunk) chunk).getHandle(); + nmsChunk.d(true); // Set Modified + nmsChunk.mustNotSave = false; + nmsChunk.markDirty(); + net.minecraft.server.v1_14_R1.World nmsWorld = nmsChunk.world; + ChunkSection[] sections = nmsChunk.getSections(); + List[] entities = nmsChunk.getEntitySlices(); + Map tiles = nmsChunk.getTileEntities(); + // Remove entities + HashSet entsToRemove = this.getEntityRemoves(); + if (!entsToRemove.isEmpty()) { + for (int i = 0; i < entities.length; i++) { + Collection ents = entities[i]; + if (!ents.isEmpty()) { + Iterator iter = ents.iterator(); + while (iter.hasNext()) { + Entity entity = iter.next(); + if (entsToRemove.contains(entity.getUniqueID())) { + if (copy != null) { + copy.storeEntity(entity); + } + iter.remove(); + synchronized (BukkitQueue_0.class) { + removeEntity(entity); + } + } + } + } + } + } + for (int i = 0; i < entities.length; i++) { + int count = this.getCount(i); + if (count == 0 || settings.EXPERIMENTAL.KEEP_ENTITIES_IN_BLOCKS) { + continue; + } else if (count >= 4096) { + Collection ents = entities[i]; + if (!ents.isEmpty()) { + synchronized (BukkitQueue_0.class) { + Iterator iter = ents.iterator(); + while (iter.hasNext()) { + Entity entity = iter.next(); + if (entity instanceof EntityPlayer) { + continue; + } + iter.remove(); + if (copy != null) { + copy.storeEntity(entity); + } + removeEntity(entity); + } + } + } + } else { + Collection ents = entities[i]; + if (!ents.isEmpty()) { + int layerYStart = i << 4; + int layerYEnd = layerYStart + 15; + int[] array = this.getIdArray(i); + if (array == null) continue; + Iterator iter = ents.iterator(); + while (iter.hasNext()) { + Entity entity = iter.next(); + if (entity instanceof EntityPlayer) { + continue; + } + int y = MathMan.roundInt(entity.locY); + if (y > layerYEnd || y < layerYStart) continue; + int x = (MathMan.roundInt(entity.locX) & 15); + int z = (MathMan.roundInt(entity.locZ) & 15); + + int index = (((y & 0xF) << 8) | (z << 4) | x); + if (array[index] != 0) { + if (copy != null) { + copy.storeEntity(entity); + } + iter.remove(); + synchronized (BukkitQueue_0.class) { + removeEntity(entity); + } + } + } + } + } + } + // Set entities + Set entitiesToSpawn = this.getEntities(); + if (!entitiesToSpawn.isEmpty()) { + synchronized (BukkitQueue_0.class) { + for (CompoundTag nativeTag : entitiesToSpawn) { + Map entityTagMap = ReflectionUtils.getMap(nativeTag.getValue()); + StringTag idTag = (StringTag) entityTagMap.get("Id"); + ListTag posTag = (ListTag) entityTagMap.get("Pos"); + ListTag rotTag = (ListTag) entityTagMap.get("Rotation"); + if (idTag == null || posTag == null || rotTag == null) { + Fawe.debug("Unknown entity tag: " + nativeTag); + continue; + } + double x = posTag.getDouble(0); + double y = posTag.getDouble(1); + double z = posTag.getDouble(2); + float yaw = rotTag.getFloat(0); + float pitch = rotTag.getFloat(1); + String id = idTag.getValue(); + EntityTypes type = EntityTypes.a(id).orElse(null); + if (type != null) { + Entity entity = type.a(nmsWorld); + if (entity != null) { + UUID uuid = entity.getUniqueID(); + entityTagMap.put("UUIDMost", new LongTag(uuid.getMostSignificantBits())); + entityTagMap.put("UUIDLeast", new LongTag(uuid.getLeastSignificantBits())); + if (nativeTag != null) { + NBTTagCompound tag = (NBTTagCompound) BukkitQueue_1_14.fromNative(nativeTag); + for (String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) { + tag.remove(name); + } + entity.f(tag); + } + entity.setLocation(x, y, z, yaw, pitch); + synchronized (BukkitQueue_0.class) { + nmsWorld.addEntity(entity, CreatureSpawnEvent.SpawnReason.CUSTOM); + } + } + } + } + } + } + // Set blocks + for (int j = 0; j < sections.length; j++) { + int count = this.getCount(j); + if (count == 0) { + continue; + } + int countAir = this.getAir(j); + final int[] array = this.getIdArray(j); + if (array == null) { + continue; + } + ChunkSection section = sections[j]; + if (copy != null) { + if (section != null) { + copy.storeSection(copy(section), j); + } + } + if (section == null) { + if (count == countAir) { + continue; + } + if (this.sectionPalettes != null && this.sectionPalettes[j] != null) { + section = sections[j] = this.sectionPalettes[j]; + continue; + } else { + section = sections[j] = getParent().newChunkSection(j, flag, array); + continue; + } + } else if (count >= 4096 && false) { + if (countAir >= 4096) { + sections[j] = null; + continue; + } + if (this.sectionPalettes != null && this.sectionPalettes[j] != null) { + section = sections[j] = this.sectionPalettes[j]; + continue; + } else { + section = sections[j] = getParent().newChunkSection(j, flag, array); + continue; + } + } + int by = j << 4; + DataPaletteBlock nibble = section.getBlocks(); + int nonEmptyBlockCount = 0; + IBlockData existing; + + for (int y = 0, i = 0; y < 16; y++) { + for (int z = 0; z < 16; z++) { + for (int x= 0; x < 16; x++, i++) { + int combinedId = array[i]; + switch (combinedId) { + case 0: + continue; + case BlockID.AIR: + case BlockID.CAVE_AIR: + case BlockID.VOID_AIR: + existing = nibble.a(x, y, z); + if (!existing.isAir()) { +// if (existing.e() > 0) { +// getParent().getRelighter().addLightUpdate(bx + x, by + y, bz + z); +// } + nonEmptyBlockCount--; + nibble.setBlock(x, y, z, AIR); + } + continue; + default: + existing = nibble.a(x, y, z); + if (!existing.isAir()) { +// if (existing.e() > 0) { +// getParent().getRelighter().addLightUpdate(bx + x, by + y, bz + z); +// } + } else { + nonEmptyBlockCount++; + } + BlockState state = BlockState.getFromInternalId(combinedId); + IBlockData ibd = ((BlockMaterial_1_14) state.getMaterial()).getState(); + nibble.setBlock(x, y, z, ibd); + } + } + } + } + getParent().setCount(0, getParent().getNonEmptyBlockCount(section) + nonEmptyBlockCount, section); + } + + // Trim tiles + HashMap toRemove = null; + if (!tiles.isEmpty()) { + Iterator> iterator = tiles.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry tile = iterator.next(); + BlockPosition pos = tile.getKey(); + int lx = pos.getX() & 15; + int ly = pos.getY(); + int lz = pos.getZ() & 15; + int layer = ly >> 4; + int[] array = this.getIdArray(layer); + if (array == null) { + continue; + } + int index = (((ly & 0xF) << 8) | (lz << 4) | lx); + if (array[index] != 0) { + if (toRemove == null) { + toRemove = new HashMap<>(); + } + if (copy != null) { + copy.storeTile(tile.getValue(), tile.getKey()); + } + toRemove.put(tile.getKey(), tile.getValue()); + } + } + if (toRemove != null) { + synchronized (BukkitQueue_0.class) { + for (Map.Entry entry : toRemove.entrySet()) { + BlockPosition bp = entry.getKey(); + TileEntity tile = entry.getValue(); + nmsWorld.removeTileEntity(bp); + tiles.remove(bp); + tile.n(); + tile.invalidateBlockCache(); + } + } + } + } + + // Set biomes + if (this.biomes != null) { + BiomeBase[] currentBiomes = nmsChunk.getBiomeIndex(); + if (copy != null) { + copy.storeBiomes(currentBiomes); + } + for (int i = 0 ; i < this.biomes.length; i++) { + BiomeType biome = this.biomes[i]; + if (biome != null) { + Biome craftBiome = adapter.adapt(biome); + currentBiomes[i] = CraftBlock.biomeToBiomeBase(craftBiome); + } + } + } + // Set tiles + Map tilesToSpawn = this.getTiles(); + if (!tilesToSpawn.isEmpty()) { + for (Map.Entry entry : tilesToSpawn.entrySet()) { + CompoundTag nativeTag = entry.getValue(); + short blockHash = entry.getKey(); + int x = (blockHash >> 12 & 0xF) + bx; + int y = (blockHash & 0xFF); + int z = (blockHash >> 8 & 0xF) + bz; + BlockPosition pos = new BlockPosition(x, y, z); // Set pos + synchronized (BukkitQueue_0.class) { + TileEntity tileEntity = nmsWorld.getTileEntity(pos); + if (tileEntity != null) { + NBTTagCompound tag = (NBTTagCompound) BukkitQueue_1_14.fromNative(nativeTag); + tag.set("x", new NBTTagInt(x)); + tag.set("y", new NBTTagInt(y)); + tag.set("z", new NBTTagInt(z)); + tileEntity.load(tag); + } + } + } + } + // Change task + if (copy != null) { + getParent().getChangeTask().run(copy, this); + } + } catch (Throwable e) { + MainUtil.handleError(e); + } + return this; + } +} \ No newline at end of file diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v1_14/BukkitQueue_1_14.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v1_14/BukkitQueue_1_14.java new file mode 100644 index 000000000..21d098f72 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v1_14/BukkitQueue_1_14.java @@ -0,0 +1,938 @@ +package com.boydti.fawe.bukkit.v1_14; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.FaweCache; +import com.boydti.fawe.bukkit.BukkitPlayer; +import com.boydti.fawe.bukkit.v0.BukkitQueue_0; +import com.boydti.fawe.bukkit.v1_14.adapter.BlockMaterial_1_14; +import com.boydti.fawe.bukkit.v1_14.adapter.Spigot_v1_14_R1; +import com.boydti.fawe.config.Settings; +import com.boydti.fawe.example.IntFaweChunk; +import com.boydti.fawe.jnbt.anvil.BitArray4096; +import com.boydti.fawe.object.FaweChunk; +import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.object.RegionWrapper; +import com.boydti.fawe.object.brush.visualization.VisualChunk; +import com.boydti.fawe.object.visitor.FaweChunkVisitor; +import com.boydti.fawe.util.MainUtil; +import com.boydti.fawe.util.MathMan; +import com.boydti.fawe.util.ReflectionUtils; +import com.boydti.fawe.util.TaskManager; +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.worldedit.world.biome.BiomeType; +import com.sk89q.worldedit.world.block.BlockID; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockTypes; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import net.minecraft.server.v1_14_R1.BiomeBase; +import net.minecraft.server.v1_14_R1.Block; +import net.minecraft.server.v1_14_R1.BlockPosition; +import net.minecraft.server.v1_14_R1.ChunkProviderServer; +import net.minecraft.server.v1_14_R1.ChunkSection; +import net.minecraft.server.v1_14_R1.ChunkStatus; +import net.minecraft.server.v1_14_R1.DataBits; +import net.minecraft.server.v1_14_R1.DataPalette; +import net.minecraft.server.v1_14_R1.DataPaletteBlock; +import net.minecraft.server.v1_14_R1.DataPaletteHash; +import net.minecraft.server.v1_14_R1.DataPaletteLinear; +import net.minecraft.server.v1_14_R1.Entity; +import net.minecraft.server.v1_14_R1.EntityPlayer; +import net.minecraft.server.v1_14_R1.EnumSkyBlock; +import net.minecraft.server.v1_14_R1.GameProfileSerializer; +import net.minecraft.server.v1_14_R1.IBlockData; +import net.minecraft.server.v1_14_R1.IChunkAccess; +import net.minecraft.server.v1_14_R1.NBTTagCompound; +import net.minecraft.server.v1_14_R1.Packet; +import net.minecraft.server.v1_14_R1.PacketDataSerializer; +import net.minecraft.server.v1_14_R1.PacketPlayOutMultiBlockChange; +import net.minecraft.server.v1_14_R1.PlayerChunk; +import net.minecraft.server.v1_14_R1.PlayerChunkMap; +import net.minecraft.server.v1_14_R1.RegistryID; +import net.minecraft.server.v1_14_R1.TileEntity; +import net.minecraft.server.v1_14_R1.WorldChunkManager; +import net.minecraft.server.v1_14_R1.WorldData; +import net.minecraft.server.v1_14_R1.WorldServer; +import org.bukkit.Chunk; +import org.bukkit.World; +import org.bukkit.craftbukkit.v1_14_R1.CraftChunk; +import org.bukkit.craftbukkit.v1_14_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_14_R1.block.CraftBlock; +import org.bukkit.craftbukkit.v1_14_R1.entity.CraftPlayer; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.atomic.LongAdder; +import java.util.function.Supplier; + +public class BukkitQueue_1_14 extends BukkitQueue_0 { + + protected final static Field fieldBits; + protected final static Field fieldPalette; + protected final static Field fieldSize; + + protected final static Field fieldHashBlocks; + protected final static Field fieldLinearBlocks; + protected final static Field fieldHashIndex; + protected final static Field fieldRegistryb; + protected final static Field fieldRegistryc; + protected final static Field fieldRegistryd; + protected final static Field fieldRegistrye; + protected final static Field fieldRegistryf; + + protected final static Field fieldLinearIndex; + protected final static Field fieldDefaultBlock; + + protected final static Field fieldFluidCount; + protected final static Field fieldTickingBlockCount; + protected final static Field fieldNonEmptyBlockCount; + protected final static Field fieldSection; + protected final static Field fieldLiquidCount; + protected final static Field fieldEmittedLight; + protected final static Field fieldSkyLight; + + +// protected final static Field fieldBiomes; + + protected final static Field fieldChunkGenerator; + protected final static Field fieldSeed; +// protected final static Field fieldBiomeCache; +// protected final static Field fieldBiomes2; + protected final static Field fieldGenLayer1; + protected final static Field fieldGenLayer2; + protected final static Field fieldSave; +// protected final static MutableGenLayer genLayer; + protected final static ChunkSection emptySection; + +// protected static final Method methodResize; + + protected final static Field fieldDirtyCount; + protected final static Field fieldDirtyBits; + + static { + try { + emptySection = new ChunkSection(0); + fieldSection = ChunkSection.class.getDeclaredField("blockIds"); + fieldLiquidCount = ChunkSection.class.getDeclaredField("e"); + fieldEmittedLight = ChunkSection.class.getDeclaredField("emittedLight"); + fieldSkyLight = ChunkSection.class.getDeclaredField("skyLight"); + fieldSection.setAccessible(true); + fieldLiquidCount.setAccessible(true); + fieldEmittedLight.setAccessible(true); + fieldSkyLight.setAccessible(true); + + fieldFluidCount = ChunkSection.class.getDeclaredField("e"); + fieldTickingBlockCount = ChunkSection.class.getDeclaredField("tickingBlockCount"); + fieldNonEmptyBlockCount = ChunkSection.class.getDeclaredField("nonEmptyBlockCount"); + fieldFluidCount.setAccessible(true); + fieldTickingBlockCount.setAccessible(true); + fieldNonEmptyBlockCount.setAccessible(true); + + + +// fieldBiomes = ChunkProviderGenerate.class.getDeclaredField("D"); // * +// fieldBiomes.setAccessible(true); + + fieldChunkGenerator = ChunkProviderServer.class.getDeclaredField("chunkGenerator"); + fieldChunkGenerator.setAccessible(true); + fieldSeed = WorldData.class.getDeclaredField("e"); + fieldSeed.setAccessible(true); + +// fieldBiomeCache = WorldChunkManager.class.getDeclaredField("d"); // * +// fieldBiomeCache.setAccessible(true); +// fieldBiomes2 = WorldChunkManager.class.getDeclaredField("e"); // * +// fieldBiomes2.setAccessible(true); + fieldGenLayer1 = WorldChunkManager.class.getDeclaredField("b") ; + fieldGenLayer2 = WorldChunkManager.class.getDeclaredField("c") ; + fieldGenLayer1.setAccessible(true); + fieldGenLayer2.setAccessible(true); + + fieldSave = ReflectionUtils.setAccessible(net.minecraft.server.v1_14_R1.Chunk.class.getDeclaredField("s")); //* + + fieldHashBlocks = DataPaletteHash.class.getDeclaredField("b"); + fieldHashBlocks.setAccessible(true); + fieldLinearBlocks = DataPaletteLinear.class.getDeclaredField("b"); + fieldLinearBlocks.setAccessible(true); + + fieldHashIndex = DataPaletteHash.class.getDeclaredField("f"); + fieldHashIndex.setAccessible(true); + + fieldRegistryb = RegistryID.class.getDeclaredField("b"); + fieldRegistryc = RegistryID.class.getDeclaredField("c"); + fieldRegistryd = RegistryID.class.getDeclaredField("d"); + fieldRegistrye = RegistryID.class.getDeclaredField("e"); + fieldRegistryf = RegistryID.class.getDeclaredField("f"); + fieldRegistryb.setAccessible(true); + fieldRegistryc.setAccessible(true); + fieldRegistryd.setAccessible(true); + fieldRegistrye.setAccessible(true); + fieldRegistryf.setAccessible(true); + + fieldLinearIndex = DataPaletteLinear.class.getDeclaredField("f"); + fieldLinearIndex.setAccessible(true); + + fieldDefaultBlock = DataPaletteBlock.class.getDeclaredField("g"); + fieldDefaultBlock.setAccessible(true); + + fieldSize = DataPaletteBlock.class.getDeclaredField("i"); + fieldSize.setAccessible(true); + + fieldBits = DataPaletteBlock.class.getDeclaredField("a"); + fieldBits.setAccessible(true); + + fieldPalette = DataPaletteBlock.class.getDeclaredField("h"); + fieldPalette.setAccessible(true); + +// methodResize = DataPaletteBlock.class.getDeclaredMethod("b", int.class); +// methodResize.setAccessible(true); + + fieldDirtyCount = PlayerChunk.class.getDeclaredField("dirtyCount"); + fieldDirtyBits = PlayerChunk.class.getDeclaredField("h"); + fieldDirtyCount.setAccessible(true); + fieldDirtyBits.setAccessible(true); + + Fawe.debug("Using adapter: " + getAdapter()); + Fawe.debug("========================================="); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + public BukkitQueue_1_14(final com.sk89q.worldedit.world.World world) { + super(world); + getImpWorld(); + } + + public BukkitQueue_1_14(final String world) { + super(world); + getImpWorld(); + } + + @Override + public ChunkSection[] getSections(net.minecraft.server.v1_14_R1.Chunk chunk) { + return chunk.getSections(); + } + + @Override + public net.minecraft.server.v1_14_R1.Chunk loadChunk(World world, int x, int z, boolean generate) { + ChunkProviderServer provider = ((CraftWorld) world).getHandle().getChunkProvider(); + IChunkAccess chunk; + if (generate) { + chunk = provider.getChunkAt(x, z, ChunkStatus.FEATURES, true); + } else { + chunk = provider.getChunkAt(x, z, ChunkStatus.FEATURES, false); + } + return (net.minecraft.server.v1_14_R1.Chunk) chunk; + } + + @Override + public ChunkSection[] getCachedSections(World world, int cx, int cz) { + net.minecraft.server.v1_14_R1.Chunk chunk = (net.minecraft.server.v1_14_R1.Chunk) ((CraftWorld) world).getHandle().getChunkProvider().getChunkAt(cx, cz, ChunkStatus.FEATURES, false); + if (chunk != null) { + return chunk.getSections(); + } + return null; + } + + @Override + public net.minecraft.server.v1_14_R1.Chunk getCachedChunk(World world, int cx, int cz) { + return (net.minecraft.server.v1_14_R1.Chunk) ((CraftWorld) world).getHandle().getChunkProvider().getChunkAt(cx, cz, ChunkStatus.FEATURES, false); + } + + @Override + public ChunkSection getCachedSection(ChunkSection[] chunkSections, int cy) { + return chunkSections[cy]; + } + + @Override + public void saveChunk(net.minecraft.server.v1_14_R1.Chunk nmsChunk) { + nmsChunk.d(true); // Set Modified + nmsChunk.mustNotSave = false; + nmsChunk.markDirty(); + } + + @Override + public boolean regenerateChunk(World world, int x, int z, BiomeType biome, Long seed) { +// if (biome != null) { +// try { +// if (seed == null) { +// seed = world.getSeed(); +// } +// nmsWorld.worldData.getSeed(); +// boolean result; +// ChunkProviderGenerate generator = new ChunkProviderGenerate(nmsWorld, seed, false, ""); +// Biome bukkitBiome = getAdapter().getBiome(biome.getId()); +// BiomeBase base = BiomeBase.getBiome(biome.getId()); +// fieldBiomes.set(generator, new BiomeBase[]{base}); +// boolean cold = base.getTemperature() <= 1; +// net.minecraft.server.v1_14_R1.ChunkGenerator existingGenerator = nmsWorld.getChunkProvider().chunkGenerator; +// long existingSeed = world.getSeed(); +// { +// if (genLayer == null) genLayer = new MutableGenLayer(seed); +// genLayer.set(biome.getId()); +// Object existingGenLayer1 = fieldGenLayer1.get(nmsWorld.getWorldChunkManager()); +// Object existingGenLayer2 = fieldGenLayer2.get(nmsWorld.getWorldChunkManager()); +// fieldGenLayer1.set(nmsWorld.getWorldChunkManager(), genLayer); +// fieldGenLayer2.set(nmsWorld.getWorldChunkManager(), genLayer); +// +// fieldSeed.set(nmsWorld.worldData, seed); +// +// ReflectionUtils.setFailsafeFieldValue(fieldBiomeCache, this.nmsWorld.getWorldChunkManager(), new BiomeCache(this.nmsWorld.getWorldChunkManager())); +// +// ReflectionUtils.setFailsafeFieldValue(fieldChunkGenerator, this.nmsWorld.getChunkProvider(), generator); +// +// keepLoaded.remove(MathMan.pairInt(x, z)); +// result = getWorld().regenerateChunk(x, z); +// net.minecraft.server.v1_14_R1.Chunk nmsChunk = getCachedChunk(world, x, z); +// if (nmsChunk != null) { +// nmsChunk.f(true); // Set Modified +// nmsChunk.mustSave = true; +// } +// +// ReflectionUtils.setFailsafeFieldValue(fieldChunkGenerator, this.nmsWorld.getChunkProvider(), existingGenerator); +// +// fieldSeed.set(nmsWorld.worldData, existingSeed); +// +// fieldGenLayer1.set(nmsWorld.getWorldChunkManager(), existingGenLayer1); +// fieldGenLayer2.set(nmsWorld.getWorldChunkManager(), existingGenLayer2); +// } +// return result; +// } catch (Throwable e) { +// e.printStackTrace(); +// } +// } + return super.regenerateChunk(world, x, z, biome, seed); + } + + @Override + public boolean setMCA(final int mcaX, final int mcaZ, final RegionWrapper allowed, final Runnable whileLocked, final boolean saveChunks, final boolean load) { + throw new UnsupportedOperationException("Anvil not implemented yet"); +// TaskManager.IMP.sync(new RunnableVal() { +// @Override +// public void run(Boolean value) { +// long start = System.currentTimeMillis(); +// long last = start; +// synchronized (RegionFileCache.class) { +// World world = getWorld(); +// if (world.getKeepSpawnInMemory()) world.setKeepSpawnInMemory(false); +// ChunkProviderServer provider = nmsWorld.getChunkProvider(); +// +// boolean mustSave = false; +// boolean[][] chunksUnloaded = null; +// { // Unload chunks +// Iterator iter = provider.a().iterator(); +// while (iter.hasNext()) { +// net.minecraft.server.v1_14_R1.Chunk chunk = iter.next(); +// if (chunk.locX >> 5 == mcaX && chunk.locZ >> 5 == mcaZ) { +// boolean isIn = allowed.isInChunk(chunk.locX, chunk.locZ); +// if (isIn) { +// if (!load) { +// mustSave |= saveChunks && save(chunk, provider); +// continue; +// } +// iter.remove(); +// boolean save = saveChunks && chunk.a(false); +// mustSave |= save; +// provider.unloadChunk(chunk, save); +// if (chunksUnloaded == null) { +// chunksUnloaded = new boolean[32][]; +// } +// int relX = chunk.locX & 31; +// boolean[] arr = chunksUnloaded[relX]; +// if (arr == null) { +// arr = chunksUnloaded[relX] = new boolean[32]; +// } +// arr[chunk.locZ & 31] = true; +// } +// } +// } +// } +// if (mustSave) { +// provider.c(); // TODO only the necessary chunks +// } +// +// File unloadedRegion = null; +// if (load && !RegionFileCache.a.isEmpty()) { +// Map map = RegionFileCache.a; +// Iterator> iter = map.entrySet().iterator(); +// String requiredPath = world.getName() + File.separator + "region"; +// while (iter.hasNext()) { +// Map.Entry entry = iter.next(); +// File file = entry.getKey(); +// int[] regPos = MainUtil.regionNameToCoords(file.getPath()); +// if (regPos[0] == mcaX && regPos[1] == mcaZ && file.getPath().contains(requiredPath)) { +// if (file.exists()) { +// unloadedRegion = file; +// RegionFile regionFile = entry.getValue(); +// iter.remove(); +// try { +// regionFile.c(); +// } catch (IOException e) { +// e.printStackTrace(); +// } +// } +// break; +// } +// } +// } +// +// long now = System.currentTimeMillis(); +// if (whileLocked != null) whileLocked.run(); +// if (!load) return; +// +// { // Load the region again +// if (unloadedRegion != null && chunksUnloaded != null && unloadedRegion.exists()) { +// final boolean[][] finalChunksUnloaded = chunksUnloaded; +// TaskManager.IMP.async(() -> { +// int bx = mcaX << 5; +// int bz = mcaZ << 5; +// for (int x = 0; x < finalChunksUnloaded.length; x++) { +// boolean[] arr = finalChunksUnloaded[x]; +// if (arr != null) { +// for (int z = 0; z < arr.length; z++) { +// if (arr[z]) { +// int cx = bx + x; +// int cz = bz + z; +// SetQueue.IMP.addTask(new Runnable() { +// @Override +// public void run() { +// net.minecraft.server.v1_14_R1.Chunk chunk = provider.getChunkAt(cx, cz, null, false); +// if (chunk != null) { +// PlayerChunk pc = getPlayerChunk(nmsWorld, cx, cz); +// if (pc != null) { +// sendChunk(pc, chunk, 0); +// } +// } +// } +// }); +// } +// } +// } +// } +// }); +// } +// } +// } +// } +// }); +// return true; + } + + @Override + public boolean next(int amount, long time) { + return super.next(amount, time); + } + + @Override + public void setSkyLight(ChunkSection section, int x, int y, int z, int value) { +// section.getSkyLightArray().a(x & 15, y & 15, z & 15, value); + } + + @Override + public void setBlockLight(ChunkSection section, int x, int y, int z, int value) { +// section.getEmittedLightArray().a(x & 15, y & 15, z & 15, value); + } + +// @Override +// public World createWorld(final WorldCreator creator) { +// final String name = creator.name(); +// ChunkGenerator generator = creator.generator(); +// final CraftServer server = (CraftServer) Bukkit.getServer(); +// final MinecraftServer console = server.getServer(); +// final File folder = new File(server.getWorldContainer(), name); +// final World world = server.getWorld(name); +// final WorldType type = WorldType.getType(creator.type().getName()); +// final boolean generateStructures = creator.generateStructures(); +// if (world != null) { +// return world; +// } +// if (folder.exists() && !folder.isDirectory()) { +// throw new IllegalArgumentException("File exists with the name '" + name + "' and isn't a folder"); +// } +// TaskManager.IMP.sync(new RunnableVal() { +// @Override +// public void run(Object value) { +// try { +// Field field = CraftServer.class.getDeclaredField("worlds"); +// field.setAccessible(true); +// Map existing = (Map) field.get(server); +// if (!existing.getClass().getName().contains("SynchronizedMap")) { +// field.set(server, Collections.synchronizedMap(existing)); +// } +// } catch (Throwable e) { +// e.printStackTrace(); +// } +// } +// }); +// if (generator == null) { +// generator = server.getGenerator(name); +// } +// int dimension = 10 + console.worlds.size(); +// boolean used = false; +// do { +// for (final WorldServer ws : console.worlds) { +// used = (ws.dimension == dimension); +// if (used) { +// ++dimension; +// break; +// } +// } +// } while (used); +// final boolean hardcore = false; +// final IDataManager sdm = new ServerNBTManager(server.getWorldContainer(), name, true, server.getHandle().getServer().dataConverterManager); +// WorldData worlddata = sdm.getWorldData(); +// final WorldSettings worldSettings; +// if (worlddata == null) { +// worldSettings = new WorldSettings(creator.seed(), EnumGamemode.getById(server.getDefaultGameMode().getValue()), generateStructures, hardcore, type); +// worldSettings.setGeneratorSettings(creator.generatorSettings()); +// worlddata = new WorldData(worldSettings, name); +// } else { +// worldSettings = null; +// } +// worlddata.checkName(name); +// final WorldServer internal = (WorldServer)new WorldServer(console, sdm, worlddata, dimension, console.methodProfiler, creator.environment(), generator).b(); +// startSet(true); // Temporarily allow async chunk load since the world isn't added yet +// if (worldSettings != null) { +// internal.a(worldSettings); +// } +// endSet(true); +// internal.scoreboard = server.getScoreboardManager().getMainScoreboard().getHandle(); +// internal.tracker = new EntityTracker(internal); +// internal.addIWorldAccess(new WorldManager(console, internal)); +// internal.worldData.setDifficulty(EnumDifficulty.EASY); +// internal.setSpawnFlags(true, true); +// if (generator != null) { +// internal.getWorld().getPopulators().addAll(generator.getDefaultPopulators(internal.getWorld())); +// } +// // Add the world +// return TaskManager.IMP.sync(new RunnableVal() { +// @Override +// public void run(World value) { +// console.worlds.add(internal); +// server.getPluginManager().callEvent(new WorldInitEvent(internal.getWorld())); +// server.getPluginManager().callEvent(new WorldLoadEvent(internal.getWorld())); +// this.value = internal.getWorld(); +// } +// }); +// } + + @Override + public int getCombinedId4Data(ChunkSection lastSection, int x, int y, int z) { + DataPaletteBlock dataPalette = lastSection.getBlocks(); + IBlockData ibd = dataPalette.a(x & 15, y & 15, z & 15); + int ordinal = ((Spigot_v1_14_R1) getAdapter()).adaptToInt(ibd); + return BlockTypes.states[ordinal].getInternalId(); + } + + @Override + public BiomeType getBiome(net.minecraft.server.v1_14_R1.Chunk chunk, int x, int z) { + BiomeBase base = chunk.getBiomeIndex()[((z & 15) << 4) + (x & 15)]; + return getAdapter().adapt(CraftBlock.biomeBaseToBiome(base)); + } + + @Override + public int getOpacity(ChunkSection section, int x, int y, int z) { + DataPaletteBlock dataPalette = section.getBlocks(); + IBlockData ibd = dataPalette.a(x & 15, y & 15, z & 15); + pos.a(x, y, z); + return ibd.b(nmsWorld, pos); + } + + @Override + public int getBrightness(ChunkSection section, int x, int y, int z) { + DataPaletteBlock dataPalette = section.getBlocks(); + IBlockData ibd = dataPalette.a(x & 15, y & 15, z & 15); + return ibd.e(); + } + + @Override + public int getOpacityBrightnessPair(ChunkSection section, int x, int y, int z) { + DataPaletteBlock dataPalette = section.getBlocks(); + IBlockData ibd = dataPalette.a(x & 15, y & 15, z & 15); + pos.a(x, y, z); + int opacity = ibd.b(nmsWorld, pos); + int brightness = ibd.e(); + return MathMan.pair16(brightness, opacity); + } + + @Override + public void sendChunk(int x, int z, int bitMask) { + net.minecraft.server.v1_14_R1.Chunk chunk = getCachedChunk(getWorld(), x, z); + if (chunk != null) { + sendChunk(getPlayerChunk((WorldServer) chunk.getWorld(), x, z), chunk, bitMask); + } + } + + @Override + public void sendChunkUpdatePLIB(FaweChunk chunk, FawePlayer... players) { +// PlayerChunkMap playerManager = ((CraftWorld) getWorld()).getHandle().getPlayerChunkMap(); +// ProtocolManager manager = ProtocolLibrary.getProtocolManager(); +// WirePacket packet = null; +// try { +// for (int i = 0; i < players.length; i++) { +// CraftPlayer bukkitPlayer = ((CraftPlayer) ((BukkitPlayer) players[i]).parent); +// EntityPlayer player = bukkitPlayer.getHandle(); +// +// if (playerManager.a(player, chunk.getX(), chunk.getZ())) { +// if (packet == null) { +// byte[] data; +// byte[] buffer = new byte[8192]; +// if (chunk instanceof LazyFaweChunk) { +// chunk = (FaweChunk) chunk.getChunk(); +// } +// if (chunk instanceof MCAChunk) { +// data = new MCAChunkPacket((MCAChunk) chunk, true, true, hasSky()).apply(buffer); +// } else { +// data = new FaweChunkPacket(chunk, true, true, hasSky()).apply(buffer); +// } +// packet = new WirePacket(PacketType.Play.Server.MAP_CHUNK, data); +// } +// manager.sendWirePacket(bukkitPlayer, packet); +// } +// } +// } catch (InvocationTargetException e) { +// throw new RuntimeException(e); +// } + super.sendChunkUpdatePLIB(chunk, players); // TODO remove + } + + @Override + public void sendBlockUpdate(FaweChunk chunk, FawePlayer... players) { + try { + PlayerChunkMap playerManager = ((CraftWorld) getWorld()).getHandle().getChunkProvider().playerChunkMap; + boolean watching = false; + boolean[] watchingArr = new boolean[players.length]; + for (int i = 0; i < players.length; i++) { + EntityPlayer player = ((CraftPlayer) ((BukkitPlayer) players[i]).parent).getHandle(); + if (playerManager.visibleChunks.get(player, chunk.getX(), chunk.getZ())) { + watchingArr[i] = true; + watching = true; + } + } + if (!watching) return; + final LongAdder size = new LongAdder(); + if (chunk instanceof VisualChunk) { + size.add(((VisualChunk) chunk).size()); + } else if (chunk instanceof IntFaweChunk) { + size.add(((IntFaweChunk) chunk).getTotalCount()); + } else { + chunk.forEachQueuedBlock(new FaweChunkVisitor() { + @Override + public void run(int localX, int y, int localZ, int combined) { + size.add(1); + } + }); + } + if (size.intValue() == 0) return; + PacketPlayOutMultiBlockChange packet = new PacketPlayOutMultiBlockChange(); + ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(); + final PacketDataSerializer buffer = new PacketDataSerializer(byteBuf); + buffer.writeInt(chunk.getX()); + buffer.writeInt(chunk.getZ()); + buffer.d(size.intValue()); + chunk.forEachQueuedBlock(new FaweChunkVisitor() { + @Override + public void run(int localX, int y, int localZ, int combined) { + short index = (short) (localX << 12 | localZ << 8 | y); + if (combined < 16) combined = 0; + buffer.writeShort(index); + buffer.d(combined); + } + }); + packet.a(buffer); + for (int i = 0; i < players.length; i++) { + if (watchingArr[i]) ((CraftPlayer) ((BukkitPlayer) players[i]).parent).getHandle().playerConnection.sendPacket(packet); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void refreshChunk(FaweChunk fc) { + sendChunk(fc.getX(), fc.getZ(), fc.getBitMask()); + } + + public void sendPacket(int cx, int cz, Packet packet) { + PlayerChunk chunk = getPlayerChunk(nmsWorld, cx, cz); + if (chunk != null) { + for (EntityPlayer player : chunk.players) { + player.playerConnection.sendPacket(packet); + } + } + } + + private PlayerChunk getPlayerChunk(WorldServer w, int cx, int cz) { + PlayerChunkMap chunkMap = w.getChunkProvider().playerChunkMap; + PlayerChunk playerChunk = chunkMap.getChunk(cx, cz); + if (playerChunk == null) { + return null; + } + if (playerChunk.players.isEmpty()) { + return null; + } + return playerChunk; + } + + public boolean sendChunk(PlayerChunk playerChunk, net.minecraft.server.v1_14_R1.Chunk nmsChunk, int mask) { + if (playerChunk == null) { + return false; + } + if (playerChunk.e()) { + ChunkSection[] sections = nmsChunk.getSections(); + for (int layer = 0; layer < 16; layer++) { + if (sections[layer] == null && (mask & (1 << layer)) != 0) { + sections[layer] = new ChunkSection(layer << 4, nmsWorld.worldProvider.g()); + } + } + TaskManager.IMP.sync(new Supplier() { + @Override + public Object get() { + try { + int dirtyBits = fieldDirtyBits.getInt(playerChunk); + if (dirtyBits == 0) { + ((CraftWorld) getWorld()).getHandle().getPlayerChunkMap().a(playerChunk); + } + if (mask == 0) { + dirtyBits = 65535; + } else { + dirtyBits |= mask; + } + + fieldDirtyBits.set(playerChunk, dirtyBits); + fieldDirtyCount.set(playerChunk, 64); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + return null; + } + }); + } +// if (mask == 0) { +// PacketPlayOutMapChunk packet = new PacketPlayOutMapChunk(nmsChunk, 65535); +// for (EntityPlayer player : playerChunk.players) { +// player.playerConnection.sendPacket(packet); +// } +// return true; +// } +// // Send chunks +// boolean empty = false; +// ChunkSection[] sections = nmsChunk.getSections(); +// for (int i = 0; i < sections.length; i++) { +// if (sections[i] == null) { +// sections[i] = emptySection; +// empty = true; +// } +// } +// if (mask == 0 || mask == 65535 && hasEntities(nmsChunk)) { +// PacketPlayOutMapChunk packet = new PacketPlayOutMapChunk(nmsChunk, 65280); +// for (EntityPlayer player : playerChunk.players) { +// player.playerConnection.sendPacket(packet); +// } +// mask = 255; +// } +// PacketPlayOutMapChunk packet = new PacketPlayOutMapChunk(nmsChunk, mask); +// for (EntityPlayer player : playerChunk.players) { +// player.playerConnection.sendPacket(packet); +// } +// if (empty) { +// for (int i = 0; i < sections.length; i++) { +// if (sections[i] == emptySection) { +// sections[i] = null; +// } +// } +// } + return true; + } + + public boolean hasEntities(net.minecraft.server.v1_14_R1.Chunk nmsChunk) { + try { + final Collection[] entities = nmsChunk.entitySlices; + for (int i = 0; i < entities.length; i++) { + Collection slice = entities[i]; + if (slice != null && !slice.isEmpty()) { + return true; + } + } + } catch (Throwable ignore) {} + return false; + } + + @Override + public boolean removeSectionLighting(ChunkSection section, int layer, boolean sky) { + } + + @Override + public void setFullbright(ChunkSection[] sections) { + } + + @Override + public int getSkyLight(ChunkSection section, int x, int y, int z) { + + } + + @Override + public int getEmmittedLight(ChunkSection section, int x, int y, int z) { + } + + @Override + public void relightBlock(int x, int y, int z) { + } + + @Override + public void relightSky(int x, int y, int z) { + } + + @Override + public void relight(int x, int y, int z) { + } + + protected WorldServer nmsWorld; + + @Override + public World getImpWorld() { + World world = super.getImpWorld(); + if (world != null) { + this.nmsWorld = ((CraftWorld) world).getHandle(); + return super.getImpWorld(); + } else { + return null; + } + } + + public static void setCount(int tickingBlockCount, int nonEmptyBlockCount, ChunkSection section) throws NoSuchFieldException, IllegalAccessException { + fieldFluidCount.set(section, 0); // TODO FIXME + fieldTickingBlockCount.set(section, tickingBlockCount); + fieldNonEmptyBlockCount.set(section, nonEmptyBlockCount); + } + + public int getNonEmptyBlockCount(ChunkSection section) throws IllegalAccessException { + return (int) fieldNonEmptyBlockCount.get(section); + } + + public void setPalette(ChunkSection section, DataPaletteBlock palette) throws NoSuchFieldException, IllegalAccessException { + fieldSection.set(section, palette); + } + + public static ChunkSection newChunkSection(int y2, boolean flag, int[] blocks) { + if (blocks == null) { + return new ChunkSection(y2 << 4); + } else { + ChunkSection section = new ChunkSection(y2 << 4); + int[] blockToPalette = FaweCache.BLOCK_TO_PALETTE.get(); + int[] paletteToBlock = FaweCache.PALETTE_TO_BLOCK.get(); + long[] blockstates = FaweCache.BLOCK_STATES.get(); + int[] blocksCopy = FaweCache.SECTION_BLOCKS.get(); + try { + int num_palette = 0; + int air = 0; + for (int i = 0; i < 4096; i++) { + int stateId = blocks[i]; + switch (stateId) { + case 0: + case BlockID.AIR: + case BlockID.CAVE_AIR: + case BlockID.VOID_AIR: + stateId = BlockID.AIR; + air++; + } + int ordinal = BlockState.getFromInternalId(stateId).getOrdinal(); // TODO fixme Remove all use of BlockTypes.BIT_OFFSET so that this conversion isn't necessary + int palette = blockToPalette[ordinal]; + if (palette == Integer.MAX_VALUE) { + blockToPalette[ordinal] = palette = num_palette; + paletteToBlock[num_palette] = ordinal; + num_palette++; + } + blocksCopy[i] = palette; + } + + // 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 + } + + int blockBitArrayEnd = (bitsPerEntry * 4096) >> 6; + if (num_palette == 1) { + for (int i = 0; i < blockBitArrayEnd; i++) blockstates[i] = 0; + } else { + BitArray4096 bitArray = new BitArray4096(blockstates, bitsPerEntry); + bitArray.fromRaw(blocksCopy); + } + + // set palette & data bits + DataPaletteBlock dataPaletteBlocks = section.getBlocks(); + // private DataPalette h; + // protected DataBits a; + long[] bits = Arrays.copyOfRange(blockstates, 0, blockBitArrayEnd); + DataBits nmsBits = new DataBits(bitsPerEntry, 4096, bits); + DataPalette palette; +// palette = new DataPaletteHash<>(Block.REGISTRY_ID, bitsPerEntry, dataPaletteBlocks, GameProfileSerializer::d, GameProfileSerializer::a); + palette = new DataPaletteLinear<>(Block.REGISTRY_ID, bitsPerEntry, dataPaletteBlocks, GameProfileSerializer::d); + + // set palette + for (int i = 0; i < num_palette; i++) { + int ordinal = paletteToBlock[i]; + blockToPalette[ordinal] = Integer.MAX_VALUE; + BlockState state = BlockTypes.states[ordinal]; + IBlockData ibd = ((BlockMaterial_1_14) state.getMaterial()).getState(); + palette.a(ibd); + } + try { + fieldBits.set(dataPaletteBlocks, nmsBits); + fieldPalette.set(dataPaletteBlocks, palette); + fieldSize.set(dataPaletteBlocks, bitsPerEntry); + setCount(0, 4096 - air, section); + } catch (IllegalAccessException | NoSuchFieldException e) { + throw new RuntimeException(e); + } + + return section; + } catch (Throwable e){ + Arrays.fill(blockToPalette, Integer.MAX_VALUE); + throw e; + } + } + } + + protected BlockPosition.MutableBlockPosition pos = new BlockPosition.MutableBlockPosition(0, 0, 0); + + @Override + public CompoundTag getTileEntity(net.minecraft.server.v1_14_R1.Chunk chunk, int x, int y, int z) { + Map tiles = chunk.getTileEntities(); + pos.c(x, y, z); + TileEntity tile = tiles.get(pos); + return tile != null ? getTag(tile) : null; + } + + public CompoundTag getTag(TileEntity tile) { + try { + NBTTagCompound tag = new NBTTagCompound(); + tile.save(tag); // readTagIntoEntity + return (CompoundTag) toNative(tag); + } catch (Exception e) { + MainUtil.handleError(e); + return null; + } + } + + @Deprecated + public boolean unloadChunk(final String world, final Chunk chunk) { + net.minecraft.server.v1_14_R1.Chunk c = ((CraftChunk) chunk).getHandle(); + c.mustNotSave = true; + if (chunk.isLoaded()) { + chunk.unload(false); + } + return true; + } + + @Override + public BukkitChunk_1_14 getFaweChunk(int x, int z) { + return new BukkitChunk_1_14(this, x, z); + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v1_14/adapter/BlockMaterial_1_14.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v1_14/adapter/BlockMaterial_1_14.java new file mode 100644 index 000000000..38ea785ef --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v1_14/adapter/BlockMaterial_1_14.java @@ -0,0 +1,151 @@ +package com.boydti.fawe.bukkit.v1_14.adapter; + +import com.sk89q.util.ReflectionUtil; +import com.sk89q.worldedit.world.registry.BlockMaterial; +import net.minecraft.server.v1_14_R1.Block; +import net.minecraft.server.v1_14_R1.EnumPistonReaction; +import net.minecraft.server.v1_14_R1.IBlockData; +import net.minecraft.server.v1_14_R1.ITileEntity; +import net.minecraft.server.v1_14_R1.Material; +import org.bukkit.craftbukkit.v1_14_R1.block.data.CraftBlockData; + +public class BlockMaterial_1_14 implements BlockMaterial { + private final Block block; + private final IBlockData defaultState; + private final Material material; + private final boolean isTranslucent; + private final CraftBlockData craftBlockData; + + public BlockMaterial_1_14(Block block) { + this(block, block.getBlockData()); + } + + public BlockMaterial_1_14(Block block, IBlockData defaultState) { + this.block = block; + this.defaultState = defaultState; + this.material = defaultState.getMaterial(); + this.craftBlockData = CraftBlockData.fromData(defaultState); + this.isTranslucent = ReflectionUtil.getField(Block.class, block, "n"); + } + + 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 defaultState.g(); + } + + @Override + public boolean isOpaque() { + return material.f(); + } + + @Override + public boolean isPowerSource() { + return defaultState.isPowerSource(); + } + + @Override + public boolean isLiquid() { + return material.isLiquid(); + } + + @Override + public boolean isSolid() { + return material.isBuildable(); + } + + @Override + public float getHardness() { + return block.strength; + } + + @Override + public float getResistance() { + return block.getDurability(); + } + + @Override + public float getSlipperiness() { + return block.m(); + } + + @Override + public int getLightValue() { + return defaultState.e(); + } + + @Override + public int getLightOpacity() { + return isTranslucent() ? 15 : 0; + } + + @Override + public boolean isFragileWhenPushed() { + return material.getPushReaction() == EnumPistonReaction.DESTROY; + } + + @Override + public boolean isUnpushable() { + return material.getPushReaction() == EnumPistonReaction.BLOCK; + } + + @Override + public boolean isTicksRandomly() { + return block.isTicking(defaultState); + } + + @Override + public boolean isMovementBlocker() { + return material.isSolid(); + } + + @Override + public boolean isBurnable() { + return material.isBurnable(); + } + + @Override + public boolean isToolRequired() { + return !material.isAlwaysDestroyable(); + } + + @Override + public boolean isReplacedDuringPlacement() { + return material.isReplaceable(); + } + + @Override + public boolean isTranslucent() { + return isTranslucent; + } + + @Override + public boolean hasContainer() { + return block instanceof ITileEntity; + } + + @Override + public int getMapColor() { + return material.i().rgb; + } +} diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v1_14/adapter/Spigot_v1_14_R1.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v1_14/adapter/Spigot_v1_14_R1.java new file mode 100644 index 000000000..a713d0965 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/v1_14/adapter/Spigot_v1_14_R1.java @@ -0,0 +1,611 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.boydti.fawe.bukkit.v1_14.adapter; + +import com.boydti.fawe.Fawe; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Maps; +import com.sk89q.jnbt.ByteArrayTag; +import com.sk89q.jnbt.ByteTag; +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.jnbt.DoubleTag; +import com.sk89q.jnbt.EndTag; +import com.sk89q.jnbt.FloatTag; +import com.sk89q.jnbt.IntArrayTag; +import com.sk89q.jnbt.IntTag; +import com.sk89q.jnbt.ListTag; +import com.sk89q.jnbt.LongArrayTag; +import com.sk89q.jnbt.LongTag; +import com.sk89q.jnbt.NBTConstants; +import com.sk89q.jnbt.ShortTag; +import com.sk89q.jnbt.StringTag; +import com.sk89q.jnbt.Tag; +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.entity.BaseEntity; +import com.sk89q.worldedit.entity.LazyBaseEntity; +import com.sk89q.worldedit.internal.Constants; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.registry.state.BooleanProperty; +import com.sk89q.worldedit.registry.state.DirectionalProperty; +import com.sk89q.worldedit.registry.state.EnumProperty; +import com.sk89q.worldedit.registry.state.IntegerProperty; +import com.sk89q.worldedit.registry.state.Property; +import com.sk89q.worldedit.util.Direction; +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.entity.EntityType; +import com.sk89q.worldedit.world.registry.BlockMaterial; +import net.minecraft.server.v1_14_R1.Block; +import net.minecraft.server.v1_14_R1.BlockPosition; +import net.minecraft.server.v1_14_R1.BlockStateBoolean; +import net.minecraft.server.v1_14_R1.BlockStateDirection; +import net.minecraft.server.v1_14_R1.BlockStateEnum; +import net.minecraft.server.v1_14_R1.BlockStateInteger; +import net.minecraft.server.v1_14_R1.BlockStateList; +import net.minecraft.server.v1_14_R1.Chunk; +import net.minecraft.server.v1_14_R1.ChunkSection; +import net.minecraft.server.v1_14_R1.Entity; +import net.minecraft.server.v1_14_R1.EntityTypes; +import net.minecraft.server.v1_14_R1.IBlockData; +import net.minecraft.server.v1_14_R1.IBlockState; +import net.minecraft.server.v1_14_R1.INamable; +import net.minecraft.server.v1_14_R1.IRegistry; +import net.minecraft.server.v1_14_R1.MinecraftKey; +import net.minecraft.server.v1_14_R1.NBTBase; +import net.minecraft.server.v1_14_R1.NBTTagByte; +import net.minecraft.server.v1_14_R1.NBTTagByteArray; +import net.minecraft.server.v1_14_R1.NBTTagCompound; +import net.minecraft.server.v1_14_R1.NBTTagDouble; +import net.minecraft.server.v1_14_R1.NBTTagEnd; +import net.minecraft.server.v1_14_R1.NBTTagFloat; +import net.minecraft.server.v1_14_R1.NBTTagInt; +import net.minecraft.server.v1_14_R1.NBTTagIntArray; +import net.minecraft.server.v1_14_R1.NBTTagList; +import net.minecraft.server.v1_14_R1.NBTTagLong; +import net.minecraft.server.v1_14_R1.NBTTagLongArray; +import net.minecraft.server.v1_14_R1.NBTTagShort; +import net.minecraft.server.v1_14_R1.NBTTagString; +import net.minecraft.server.v1_14_R1.PlayerChunkMap; +import net.minecraft.server.v1_14_R1.TileEntity; +import net.minecraft.server.v1_14_R1.World; +import net.minecraft.server.v1_14_R1.WorldServer; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.block.data.BlockData; +import org.bukkit.craftbukkit.v1_14_R1.CraftChunk; +import org.bukkit.craftbukkit.v1_14_R1.CraftServer; +import org.bukkit.craftbukkit.v1_14_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_14_R1.block.data.CraftBlockData; +import org.bukkit.craftbukkit.v1_14_R1.entity.CraftEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nullable; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import static com.google.common.base.Preconditions.checkNotNull; + +public final class Spigot_v1_14_R1 extends CachedBukkitAdapter implements BukkitImplAdapter{ + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private final Field nbtListTagListField; + private final Method nbtCreateTagMethod; + + static { + // A simple test + if (!Bukkit.getServer().getClass().getName().endsWith("DummyServer")) CraftServer.class.cast(Bukkit.getServer()); + } + + // ------------------------------------------------------------------------ + // Code that may break between versions of Minecraft + // ------------------------------------------------------------------------ + + public Spigot_v1_14_R1() throws NoSuchFieldException, NoSuchMethodException { + // The list of tags on an NBTTagList + nbtListTagListField = NBTTagList.class.getDeclaredField("list"); + nbtListTagListField.setAccessible(true); + + // The method to create an NBTBase tag given its type ID + nbtCreateTagMethod = NBTBase.class.getDeclaredMethod("createTag", byte.class); + nbtCreateTagMethod.setAccessible(true); + } + + public int[] idbToStateOrdinal; + + private boolean init() { + if (idbToStateOrdinal != null) return false; + idbToStateOrdinal = new int[Block.REGISTRY_ID.a()]; // size + for (int i = 0; i < idbToStateOrdinal.length; i++) { + BlockState state = BlockTypes.states[i]; + BlockMaterial_1_14 material = (BlockMaterial_1_14) state.getMaterial(); + int id = Block.REGISTRY_ID.getId(material.getState()); + idbToStateOrdinal[id] = state.getOrdinal(); + } + return true; + } + + /** + * Read the given NBT data into the given tile entity. + * + * @param tileEntity the tile entity + * @param tag the tag + */ + private static void readTagIntoTileEntity(NBTTagCompound tag, TileEntity tileEntity) { + try { + tileEntity.load(tag); + } catch (Throwable e) { + Fawe.debug("Invalid tag " + tag + " | " + tileEntity); + } + } + + /** + * Write the tile entity's NBT data to the given tag. + * + * @param tileEntity the tile entity + * @param tag the tag + */ + private static void readTileEntityIntoTag(TileEntity tileEntity, NBTTagCompound tag) { + tileEntity.save(tag); + } + + /** + * Get the ID string of the given entity. + * + * @param entity the entity + * @return the entity ID or null if one is not known + */ + @Nullable + private static String getEntityId(Entity entity) { + MinecraftKey minecraftkey = EntityTypes.getName(entity.getBukkitEntity().getHandle().getEntityType()); + return minecraftkey == null ? null : minecraftkey.toString(); + } + + /** + * Create an entity using the given entity ID. + * + * @param id the entity ID + * @param world the world + * @return an entity or null + */ + @Nullable + private static Entity createEntityFromId(String id, World world) { + return EntityTypes.a(id).get().a(world); + } + + /** + * Write the given NBT data into the given entity. + * + * @param entity the entity + * @param tag the tag + */ + private static void readTagIntoEntity(NBTTagCompound tag, Entity entity) { + entity.f(tag); + } + + /** + * Write the entity's NBT data to the given tag. + * + * @param entity the entity + * @param tag the tag + */ + private static void readEntityIntoTag(Entity entity, NBTTagCompound tag) { + entity.save(tag); + } + + @Override + public BlockMaterial getMaterial(BlockType blockType) { + return new BlockMaterial_1_14(getBlock(blockType)); + } + + @Override + public BlockMaterial getMaterial(BlockState state) { + IBlockData bs = ((CraftBlockData) Bukkit.createBlockData(state.getAsString())).getState(); + return new BlockMaterial_1_14(bs.getBlock(), bs); + } + + public Block getBlock(BlockType blockType) { + return IRegistry.BLOCK.get(new MinecraftKey(blockType.getNamespace(), blockType.getResource())); + } + + // ------------------------------------------------------------------------ + // Code that is less likely to break + // ------------------------------------------------------------------------ + + @SuppressWarnings("deprecation") + @Override + public BaseBlock getBlock(Location location) { + checkNotNull(location); + + CraftWorld craftWorld = ((CraftWorld) location.getWorld()); + int x = location.getBlockX(); + int y = location.getBlockY(); + int z = location.getBlockZ(); + + org.bukkit.block.Block bukkitBlock = location.getBlock(); + BlockState state = BukkitAdapter.adapt(bukkitBlock.getBlockData()); + if (state.getBlockType().getMaterial().hasContainer()) { + //Read the NBT data + TileEntity te = craftWorld.getHandle().getTileEntity(new BlockPosition(x, y, z)); + if (te != null) { + NBTTagCompound tag = new NBTTagCompound(); + readTileEntityIntoTag(te, tag); // Load data + return state.toBaseBlock((CompoundTag) toNative(tag)); + } + } + + return state.toBaseBlock(); + } + + @Override + public boolean isChunkInUse(org.bukkit.Chunk chunk) { + CraftChunk craftChunk = (CraftChunk) chunk; + PlayerChunkMap chunkMap = ((WorldServer) craftChunk.getHandle().getWorld()).getPlayerChunkMap(); + return chunkMap.isChunkInUse(chunk.getX(), chunk.getZ()); + } + + @Override + 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(); + + IBlockData blockData = ((BlockMaterial_1_14) state.getMaterial()).getState(); + ChunkSection[] sections = nmsChunk.getSections(); + int y4 = y >> 4; + ChunkSection section = sections[y4]; + + IBlockData existing; + if (section == null) { + existing = ((BlockMaterial_1_14) BlockTypes.AIR.getDefaultState().getMaterial()).getState(); + } else { + existing = section.getType(x & 15, y & 15, z & 15); + } + + BlockPosition pos = new BlockPosition(x, y, z); + + nmsChunk.removeTileEntity(pos); // Force delete the old tile entity + + CompoundTag nativeTag = state instanceof BaseBlock ? ((BaseBlock)state).getNbtData() : null; + if (nativeTag != null || existing instanceof TileEntityBlock) { + nmsWorld.setTypeAndData(pos, 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(pos); + if (tileEntity != null) { + NBTTagCompound tag = (NBTTagCompound) fromNative(nativeTag); + tag.set("x", new NBTTagInt(x)); + tag.set("y", new NBTTagInt(y)); + tag.set("z", new NBTTagInt(z)); + readTagIntoTileEntity(tag, tileEntity); // 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(pos = new BlockPosition(x, y, z), blockData, false); + } + if (update) { + nmsWorld.getMinecraftWorld().notify(pos, existing, blockData, 0); + } + return true; + } + + @Override + public BaseEntity getEntity(org.bukkit.entity.Entity entity) { + checkNotNull(entity); + + CraftEntity craftEntity = ((CraftEntity) entity); + Entity mcEntity = craftEntity.getHandle(); + + String id = getEntityId(mcEntity); + + if (id != null) { + EntityType type = com.sk89q.worldedit.world.entity.EntityTypes.get(id); + Supplier saveTag = new Supplier() { + @Override + public CompoundTag get() { + NBTTagCompound tag = new NBTTagCompound(); + readEntityIntoTag(mcEntity, tag); + return (CompoundTag) toNative(tag); + } + }; + return new LazyBaseEntity(type, saveTag); + } else { + return null; + } + } + + @Nullable + @Override + public org.bukkit.entity.Entity createEntity(Location location, BaseEntity state) { + checkNotNull(location); + checkNotNull(state); + if (state.getType() == com.sk89q.worldedit.world.entity.EntityTypes.PLAYER) return null; + CraftWorld craftWorld = ((CraftWorld) location.getWorld()); + WorldServer worldServer = craftWorld.getHandle(); + + Entity createdEntity = createEntityFromId(state.getType().getId(), craftWorld.getHandle()); + + if (createdEntity != null) { + CompoundTag nativeTag = state.getNbtData(); + if (nativeTag != null) { + NBTTagCompound tag = (NBTTagCompound) fromNative(nativeTag); + for (String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) { + tag.remove(name); + } + readTagIntoEntity(tag, createdEntity); + } + + createdEntity.setLocation(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); + + worldServer.addEntity(createdEntity, SpawnReason.CUSTOM); + return createdEntity.getBukkitEntity(); + } else { + Fawe.debug("Invalid entity " + state.getType().getId()); + return null; + } + } + + @SuppressWarnings("unchecked") + @Override + public Map> getProperties(BlockType blockType) { + Block block; + try { + block = IRegistry.BLOCK.get(new MinecraftKey(blockType.getNamespace(), blockType.getResource())); + } catch (Throwable e) { + e.printStackTrace(); + return Collections.emptyMap(); + } + if (block == null) { + logger.warn("Failed to find properties for " + blockType.getId()); + return Collections.emptyMap(); + } + Map> properties = Maps.newLinkedHashMap(); + BlockStateList blockStateList = block.getStates(); + for (IBlockState state : blockStateList.d()) { + Property property; + if (state instanceof BlockStateBoolean) { + property = new BooleanProperty(state.a(), ImmutableList.copyOf(state.d())); + } else if (state instanceof BlockStateDirection) { + property = new DirectionalProperty(state.a(), + (List) state.d().stream().map(e -> Direction.valueOf(((INamable) e).getName().toUpperCase())).collect(Collectors.toList())); + } else if (state instanceof BlockStateEnum) { + property = new EnumProperty(state.a(), + (List) state.d().stream().map(e -> ((INamable) e).getName()).collect(Collectors.toList())); + } else if (state instanceof BlockStateInteger) { + property = new IntegerProperty(state.a(), ImmutableList.copyOf(state.d())); + } else { + throw new IllegalArgumentException("WorldEdit needs an update to support " + state.getClass().getSimpleName()); + } + + properties.put(property.getName(), property); + } + return properties; + } + + /** + * Converts from a non-native NMS NBT structure to a native WorldEdit NBT + * structure. + * + * @param foreign non-native NMS NBT structure + * @return native WorldEdit NBT structure + */ + @SuppressWarnings("unchecked") + @Override + public Tag toNative(NBTBase foreign) { + if (foreign == null) { + return null; + } + if (foreign instanceof NBTTagCompound) { + Map values = new HashMap<>(); + Set foreignKeys = ((NBTTagCompound) foreign).getKeys(); // map.keySet + + for (String str : foreignKeys) { + NBTBase base = ((NBTTagCompound) foreign).get(str); + values.put(str, toNative(base)); + } + return new CompoundTag(values); + } else if (foreign instanceof NBTTagByte) { + return new ByteTag(((NBTTagByte) foreign).asByte()); + } else if (foreign instanceof NBTTagByteArray) { + return new ByteArrayTag(((NBTTagByteArray) foreign).getBytes()); // data + } else if (foreign instanceof NBTTagDouble) { + return new DoubleTag(((NBTTagDouble) foreign).asDouble()); // getDouble + } else if (foreign instanceof NBTTagFloat) { + return new FloatTag(((NBTTagFloat) foreign).asFloat()); + } else if (foreign instanceof NBTTagInt) { + return new IntTag(((NBTTagInt) foreign).asInt()); + } else if (foreign instanceof NBTTagIntArray) { + return new IntArrayTag(((NBTTagIntArray) foreign).getInts()); // data + } else if (foreign instanceof NBTTagLongArray) { + return new LongArrayTag(((NBTTagLongArray) foreign).getLongs()); // data + } else if (foreign instanceof NBTTagList) { + try { + return toNativeList((NBTTagList) foreign); + } catch (Throwable e) { + logger.warn("Failed to convert NBTTagList", e); + return new ListTag(ByteTag.class, new ArrayList()); + } + } else if (foreign instanceof NBTTagLong) { + return new LongTag(((NBTTagLong) foreign).asLong()); + } else if (foreign instanceof NBTTagShort) { + return new ShortTag(((NBTTagShort) foreign).asShort()); + } else if (foreign instanceof NBTTagString) { + return new StringTag(foreign.asString()); + } else if (foreign instanceof NBTTagEnd) { + return new EndTag(); + } else { + throw new IllegalArgumentException("Don't know how to make native " + foreign.getClass().getCanonicalName()); + } + } + + /** + * Convert a foreign NBT list tag into a native WorldEdit one. + * + * @param foreign the foreign tag + * @return the converted tag + * @throws NoSuchFieldException on error + * @throws SecurityException on error + * @throws IllegalArgumentException on error + * @throws IllegalAccessException on error + */ + public ListTag toNativeList(NBTTagList foreign) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { + List values = new ArrayList<>(); + int type = foreign.getTypeId(); + + List foreignList; + foreignList = (List) nbtListTagListField.get(foreign); + for (int i = 0; i < foreign.size(); i++) { + NBTBase element = (NBTBase) foreignList.get(i); + values.add(toNative(element)); // List elements shouldn't have names + } + + Class cls = NBTConstants.getClassFromType(type); + return new ListTag(cls, values); + } + + /** + * Converts a WorldEdit-native NBT structure to a NMS structure. + * + * @param foreign structure to convert + * @return non-native structure + */ + @Override + public NBTBase fromNative(Tag foreign) { + if (foreign == null) { + return null; + } + if (foreign instanceof CompoundTag) { + NBTTagCompound tag = new NBTTagCompound(); + for (Map.Entry entry : ((CompoundTag) foreign) + .getValue().entrySet()) { + tag.set(entry.getKey(), fromNative(entry.getValue())); + } + return tag; + } else if (foreign instanceof ByteTag) { + return new NBTTagByte(((ByteTag) foreign).getValue()); + } else if (foreign instanceof ByteArrayTag) { + return new NBTTagByteArray(((ByteArrayTag) foreign).getValue()); + } else if (foreign instanceof DoubleTag) { + return new NBTTagDouble(((DoubleTag) foreign).getValue()); + } else if (foreign instanceof FloatTag) { + return new NBTTagFloat(((FloatTag) foreign).getValue()); + } else if (foreign instanceof IntTag) { + return new NBTTagInt(((IntTag) foreign).getValue()); + } else if (foreign instanceof IntArrayTag) { + return new NBTTagIntArray(((IntArrayTag) foreign).getValue()); + } else if (foreign instanceof LongArrayTag) { + return new NBTTagLongArray(((LongArrayTag) foreign).getValue()); + } else if (foreign instanceof ListTag) { + NBTTagList tag = new NBTTagList(); + ListTag foreignList = (ListTag) foreign; + for (Tag t : foreignList.getValue()) { + tag.add(fromNative(t)); + } + return tag; + } else if (foreign instanceof LongTag) { + return new NBTTagLong(((LongTag) foreign).getValue()); + } else if (foreign instanceof ShortTag) { + return new NBTTagShort(((ShortTag) foreign).getValue()); + } else if (foreign instanceof StringTag) { + return new NBTTagString(((StringTag) foreign).getValue()); + } else if (foreign instanceof EndTag) { + try { + return (NBTBase) nbtCreateTagMethod.invoke(null, (byte) 0); + } catch (Exception e) { + return null; + } + } else { + throw new IllegalArgumentException("Don't know how to make NMS " + foreign.getClass().getCanonicalName()); + } + } + + @Override + public BlockState adapt(BlockData blockData) { + CraftBlockData cbd = ((CraftBlockData) blockData); + IBlockData ibd = cbd.getState(); + return adapt(ibd); + } + + public BlockState adapt(IBlockData ibd) { + return BlockTypes.states[adaptToInt(ibd)]; + } + + public int adaptToInt(IBlockData ibd) { + try { + int id = Block.REGISTRY_ID.getId(ibd); + return idbToStateOrdinal[id]; + } catch (NullPointerException e) { + if (init()) return adaptToInt(ibd); + throw e; + } + } + + @Override + public BlockData adapt(BlockStateHolder state) { + BlockMaterial_1_14 material = (BlockMaterial_1_14) state.getMaterial(); + return material.getCraftBlockData(); + } + + @Override + public void sendFakeNBT(Player player, BlockVector3 pos, CompoundTag nbtData) { + // TODO Auto-generated method stub + + } + + @Override + public void notifyAndLightBlock(Location position, BlockState previousType) { + this.setBlock(position.getChunk(), position.getBlockX(), position.getBlockY(), position.getBlockZ(), previousType, true); + } + + @Override + public boolean setBlock(Location location, BlockStateHolder state, boolean notifyAndLight) { + return this.setBlock(location.getChunk(), location.getBlockX(), location.getBlockY(), location.getBlockZ(), state, notifyAndLight); + } + + @Override + public void sendFakeOP(Player player) { + // TODO Auto-generated method stub + + } +}