/* * WorldEdit, a Minecraft world manipulation toolkit * Copyright (C) sk89q <http://www.sk89q.com> * 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 <http://www.gnu.org/licenses/>. */ package com.sk89q.worldedit.bukkit.adapter.impl; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableList; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.common.io.Files; 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.EditSession; import com.sk89q.worldedit.MaxChangedBlocksException; import com.sk89q.worldedit.blocks.BaseItem; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.bukkit.BukkitWorld; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.extension.platform.Watchdog; import com.sk89q.worldedit.internal.Constants; import com.sk89q.worldedit.internal.block.BlockStateIdAccess; import com.sk89q.worldedit.internal.wna.WorldNativeAccess; import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.regions.Region; 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.util.SideEffect; import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; import com.sk89q.worldedit.world.DataFixer; import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.block.BlockTypes; import com.sk89q.worldedit.world.item.ItemType; 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.Blocks; import net.minecraft.server.v1_14_R1.Chunk; import net.minecraft.server.v1_14_R1.ChunkCoordIntPair; import net.minecraft.server.v1_14_R1.ChunkStatus; import net.minecraft.server.v1_14_R1.DedicatedServer; import net.minecraft.server.v1_14_R1.Entity; import net.minecraft.server.v1_14_R1.EntityTypes; import net.minecraft.server.v1_14_R1.EnumDirection; import net.minecraft.server.v1_14_R1.EnumHand; import net.minecraft.server.v1_14_R1.EnumInteractionResult; 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.Item; import net.minecraft.server.v1_14_R1.ItemActionContext; import net.minecraft.server.v1_14_R1.ItemStack; import net.minecraft.server.v1_14_R1.MinecraftKey; import net.minecraft.server.v1_14_R1.MinecraftServer; import net.minecraft.server.v1_14_R1.MovingObjectPositionBlock; 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.PacketPlayOutEntityStatus; import net.minecraft.server.v1_14_R1.PacketPlayOutTileEntityData; import net.minecraft.server.v1_14_R1.SystemUtils; import net.minecraft.server.v1_14_R1.TileEntity; import net.minecraft.server.v1_14_R1.Vec3D; import net.minecraft.server.v1_14_R1.World; import net.minecraft.server.v1_14_R1.WorldData; import net.minecraft.server.v1_14_R1.WorldLoadListener; import net.minecraft.server.v1_14_R1.WorldNBTStorage; import net.minecraft.server.v1_14_R1.WorldServer; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.World.Environment; import org.bukkit.block.data.BlockData; 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.craftbukkit.v1_14_R1.entity.CraftPlayer; import org.bukkit.craftbukkit.v1_14_R1.inventory.CraftItemStack; import org.bukkit.craftbukkit.v1_14_R1.util.CraftMagicNumbers; import org.bukkit.entity.Player; import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; import org.bukkit.generator.ChunkGenerator; import org.spigotmc.SpigotConfig; import org.spigotmc.WatchdogThread; import javax.annotation.Nullable; import java.io.File; import java.io.IOException; import java.lang.ref.WeakReference; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.OptionalInt; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.ForkJoinPool; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; import static com.google.common.base.Preconditions.checkNotNull; public final class Spigot_v1_14_R4 implements BukkitImplAdapter { private final Logger logger = Logger.getLogger(getClass().getCanonicalName()); private final Field nbtListTagListField; private final Field serverWorldsField; private final Method nbtCreateTagMethod; private final Watchdog watchdog; // ------------------------------------------------------------------------ // Code that may break between versions of Minecraft // ------------------------------------------------------------------------ public Spigot_v1_14_R4() throws NoSuchFieldException, NoSuchMethodException { // A simple test CraftServer.class.cast(Bukkit.getServer()); if (getDataVersion() != 1976) throw new UnsupportedClassVersionError("Not 1.14.4!"); // The list of tags on an NBTTagList nbtListTagListField = NBTTagList.class.getDeclaredField("list"); nbtListTagListField.setAccessible(true); serverWorldsField = CraftServer.class.getDeclaredField("worlds"); serverWorldsField.setAccessible(true); // The method to create an NBTBase tag given its type ID nbtCreateTagMethod = NBTBase.class.getDeclaredMethod("createTag", byte.class); nbtCreateTagMethod.setAccessible(true); new DataConverters_1_14_R4(getDataVersion(), this).build(ForkJoinPool.commonPool()); Watchdog watchdog; try { Class.forName("org.spigotmc.WatchdogThread"); watchdog = new SpigotWatchdog(); } catch (ClassNotFoundException e) { try { watchdog = new MojangWatchdog(((CraftServer) Bukkit.getServer()).getServer()); } catch (NoSuchFieldException ex) { watchdog = null; } } this.watchdog = watchdog; try { Class.forName("org.spigotmc.SpigotConfig"); SpigotConfig.config.set("world-settings.worldeditregentempworld.verbose", false); } catch (ClassNotFoundException ignored) {} } @Override public int getDataVersion() { return CraftMagicNumbers.INSTANCE.getDataVersion(); } @Override public DataFixer getDataFixer() { return DataConverters_1_14_R4.INSTANCE; } /** * Read the given NBT data into the given tile entity. * * @param tileEntity the tile entity * @param tag the tag */ static void readTagIntoTileEntity(NBTTagCompound tag, TileEntity tileEntity) { tileEntity.load(tag); } /** * 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.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).map(t -> t.a(world)).orElse(null); } /** * 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); } private static Block getBlockFromType(BlockType blockType) { return IRegistry.BLOCK.get(MinecraftKey.a(blockType.getId())); } private static Item getItemFromType(ItemType itemType) { return IRegistry.ITEM.get(MinecraftKey.a(itemType.getId())); } @Override public OptionalInt getInternalBlockStateId(BlockData data) { IBlockData state = ((CraftBlockData) data).getState(); int combinedId = Block.getCombinedId(state); return combinedId == 0 && state.getBlock() != Blocks.AIR ? OptionalInt.empty() : OptionalInt.of(combinedId); } @Override public OptionalInt getInternalBlockStateId(BlockState state) { Block mcBlock = getBlockFromType(state.getBlockType()); IBlockData newState = mcBlock.getBlockData(); Map<Property<?>, Object> states = state.getStates(); newState = applyProperties(mcBlock.getStates(), newState, states); final int combinedId = Block.getCombinedId(newState); return combinedId == 0 && state.getBlockType() != BlockTypes.AIR ? OptionalInt.empty() : OptionalInt.of(combinedId); } @Override public BaseBlock getBlock(Location location) { checkNotNull(location); CraftWorld craftWorld = ((CraftWorld) location.getWorld()); int x = location.getBlockX(); int y = location.getBlockY(); int z = location.getBlockZ(); final WorldServer handle = craftWorld.getHandle(); Chunk chunk = handle.getChunkAt(x >> 4, z >> 4); final BlockPosition blockPos = new BlockPosition(x, y, z); final IBlockData blockData = chunk.getType(blockPos); int internalId = Block.getCombinedId(blockData); BlockState state = BlockStateIdAccess.getBlockStateById(internalId); if (state == null) { org.bukkit.block.Block bukkitBlock = location.getBlock(); state = BukkitAdapter.adapt(bukkitBlock.getBlockData()); } // Read the NBT data TileEntity te = chunk.a(blockPos, Chunk.EnumTileEntityState.CHECK); if (te != null) { NBTTagCompound tag = new NBTTagCompound(); readTileEntityIntoTag(te, tag); // Load data return state.toBaseBlock((CompoundTag) toNative(tag)); } return state.toBaseBlock(); } @Override public WorldNativeAccess<?, ?, ?> createWorldNativeAccess(org.bukkit.World world) { return new WorldNativeAccess_v1_14_R4(this, new WeakReference<>(((CraftWorld) world).getHandle())); } private static EnumDirection adapt(Direction face) { switch (face) { case NORTH: return EnumDirection.NORTH; case SOUTH: return EnumDirection.SOUTH; case WEST: return EnumDirection.WEST; case EAST: return EnumDirection.EAST; case DOWN: return EnumDirection.DOWN; case UP: default: return EnumDirection.UP; } } private IBlockData applyProperties(BlockStateList<Block, IBlockData> stateContainer, IBlockData newState, Map<Property<?>, Object> states) { for (Map.Entry<Property<?>, Object> state : states.entrySet()) { IBlockState<?> property = stateContainer.a(state.getKey().getName()); Comparable<?> value = (Comparable) state.getValue(); // we may need to adapt this value, depending on the source prop if (property instanceof BlockStateDirection) { Direction dir = (Direction) value; value = adapt(dir); } else if (property instanceof BlockStateEnum) { String enumName = (String) value; value = ((BlockStateEnum<?>) property).b((String) value).orElseGet(() -> { throw new IllegalStateException("Enum property " + property.a() + " does not contain " + enumName); }); } newState = newState.set((IBlockState) property, (Comparable) value); } return newState; } @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) { NBTTagCompound tag = new NBTTagCompound(); readEntityIntoTag(mcEntity, tag); return new BaseEntity(com.sk89q.worldedit.world.entity.EntityTypes.get(id), (CompoundTag) toNative(tag)); } else { return null; } } @Nullable @Override public org.bukkit.entity.Entity createEntity(Location location, BaseEntity state) { checkNotNull(location); checkNotNull(state); 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 { return null; } } @Override public Component getRichBlockName(BlockType blockType) { return TranslatableComponent.of(getBlockFromType(blockType).l()); } @Override public Component getRichItemName(ItemType itemType) { return TranslatableComponent.of(getItemFromType(itemType).getName()); } @Override public Component getRichItemName(BaseItemStack itemStack) { return TranslatableComponent.of(CraftItemStack.asNMSCopy(BukkitAdapter.adapt(itemStack)).j()); } @SuppressWarnings("unchecked") @Override public Map<String, ? extends Property<?>> getProperties(BlockType blockType) { Map<String, Property<?>> properties = Maps.newTreeMap(String::compareTo); Block block = getBlockFromType(blockType); if (block == null) { logger.warning("Failed to find properties for " + blockType.getId()); return properties; } BlockStateList<Block, IBlockData> blockStateList = block.getStates(); for (IBlockState state : blockStateList.d()) { Property property; if (state instanceof BlockStateBoolean) { property = new BooleanProperty(state.a(), ImmutableList.copyOf(state.getValues())); } else if (state instanceof BlockStateDirection) { property = new DirectionalProperty(state.a(), (List<Direction>) state.getValues().stream().map(e -> Direction.valueOf(((INamable) e).getName().toUpperCase())).collect(Collectors.toList())); } else if (state instanceof BlockStateEnum) { property = new EnumProperty(state.a(), (List<String>) state.getValues().stream().map(e -> ((INamable) e).getName()).collect(Collectors.toList())); } else if (state instanceof BlockStateInteger) { property = new IntegerProperty(state.a(), ImmutableList.copyOf(state.getValues())); } else { throw new IllegalArgumentException("WorldEdit needs an update to support " + state.getClass().getSimpleName()); } properties.put(property.getName(), property); } return properties; } @Override public void sendFakeNBT(Player player, BlockVector3 pos, CompoundTag nbtData) { ((CraftPlayer) player).getHandle().playerConnection.sendPacket(new PacketPlayOutTileEntityData( new BlockPosition(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ()), 7, (NBTTagCompound) fromNative(nbtData) )); } @Override public void sendFakeOP(Player player) { ((CraftPlayer) player).getHandle().playerConnection.sendPacket(new PacketPlayOutEntityStatus( ((CraftPlayer) player).getHandle(), (byte) 28 )); } @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; } private LoadingCache<WorldServer, FakePlayer_v1_14_R4> fakePlayers = CacheBuilder.newBuilder().weakKeys().softValues().build(CacheLoader.from(FakePlayer_v1_14_R4::new)); @Override public boolean simulateItemUse(org.bukkit.World world, BlockVector3 position, BaseItem item, Direction face) { CraftWorld craftWorld = (CraftWorld) world; WorldServer worldServer = craftWorld.getHandle(); ItemStack stack = CraftItemStack.asNMSCopy(BukkitAdapter.adapt(item instanceof BaseItemStack ? ((BaseItemStack) item) : new BaseItemStack(item.getType(), item.getNbtData(), 1))); stack.setTag((NBTTagCompound) fromNative(item.getNbtData())); FakePlayer_v1_14_R4 fakePlayer; try { fakePlayer = fakePlayers.get(worldServer); } catch (ExecutionException ignored) { return false; } fakePlayer.a(EnumHand.MAIN_HAND, stack); fakePlayer.setLocation(position.getBlockX(), position.getBlockY(), position.getBlockZ(), (float) face.toVector().toYaw(), (float) face.toVector().toPitch()); final BlockPosition blockPos = new BlockPosition(position.getBlockX(), position.getBlockY(), position.getBlockZ()); final Vec3D blockVec = new Vec3D(blockPos); final EnumDirection enumFacing = adapt(face); MovingObjectPositionBlock rayTrace = new MovingObjectPositionBlock(blockVec, enumFacing, blockPos, false); ItemActionContext context = new ItemActionContext(fakePlayer, EnumHand.MAIN_HAND, rayTrace); EnumInteractionResult result = stack.placeItem(context, EnumHand.MAIN_HAND); if (result != EnumInteractionResult.SUCCESS) { if (worldServer.getType(blockPos).interact(worldServer, fakePlayer, EnumHand.MAIN_HAND, rayTrace)) { result = EnumInteractionResult.SUCCESS; } else { result = stack.getItem().a(worldServer, fakePlayer, EnumHand.MAIN_HAND).a(); } } return result == EnumInteractionResult.SUCCESS; } @Override public boolean regenerate(org.bukkit.World bukkitWorld, Region region, EditSession editSession) { WorldServer originalWorld = ((CraftWorld) bukkitWorld).getHandle(); File saveFolder = Files.createTempDir(); // register this just in case something goes wrong // normally it should be deleted at the end of this method saveFolder.deleteOnExit(); try { Environment env = bukkitWorld.getEnvironment(); ChunkGenerator gen = bukkitWorld.getGenerator(); MinecraftServer server = originalWorld.getServer().getServer(); WorldData newWorldData = new WorldData(originalWorld.worldData.a((NBTTagCompound) null), server.dataConverterManager, getDataVersion(), null); newWorldData.setName("worldeditregentempworld"); WorldNBTStorage saveHandler = new WorldNBTStorage(saveFolder, originalWorld.getDataManager().getDirectory().getName(), server, server.dataConverterManager); try (WorldServer freshWorld = new WorldServer(server, server.executorService, saveHandler, newWorldData, originalWorld.worldProvider.getDimensionManager(), originalWorld.getMethodProfiler(), new NoOpWorldLoadListener(), env, gen)) { freshWorld.savingDisabled = true; // Pre-gen all the chunks // We need to also pull one more chunk in every direction CuboidRegion expandedPreGen = new CuboidRegion(region.getMinimumPoint().subtract(16, 0, 16), region.getMaximumPoint().add(16, 0, 16)); for (BlockVector2 chunk : expandedPreGen.getChunks()) { freshWorld.getChunkAt(chunk.getBlockX(), chunk.getBlockZ()); } CraftWorld craftWorld = freshWorld.getWorld(); BukkitWorld from = new BukkitWorld(craftWorld); for (BlockVector3 vec : region) { editSession.setBlock(vec, from.getFullBlock(vec)); } } catch (IOException e) { throw new RuntimeException(e); } } catch (MaxChangedBlocksException e) { throw new RuntimeException(e); } finally { saveFolder.delete(); try { Map<String, org.bukkit.World> map = (Map<String, org.bukkit.World>) serverWorldsField.get(Bukkit.getServer()); map.remove("worldeditregentempworld"); } catch (IllegalAccessException ignored) { } } return true; } private static final Set<SideEffect> SUPPORTED_SIDE_EFFECTS = Sets.immutableEnumSet( SideEffect.NEIGHBORS, SideEffect.LIGHTING, SideEffect.VALIDATION, SideEffect.ENTITY_AI, SideEffect.EVENTS ); @Override public Set<SideEffect> getSupportedSideEffects() { return SUPPORTED_SIDE_EFFECTS; } // ------------------------------------------------------------------------ // Code that is less likely to break // ------------------------------------------------------------------------ /** * 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") Tag toNative(NBTBase foreign) { if (foreign == null) { return null; } if (foreign instanceof NBTTagCompound) { Map<String, Tag> values = new HashMap<>(); Set<String> 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.log(Level.WARNING, "Failed to convert NBTTagList", e); return new ListTag(ByteTag.class, new ArrayList<ByteTag>()); } } 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 SecurityException on error * @throws IllegalArgumentException on error * @throws IllegalAccessException on error */ private ListTag toNativeList(NBTTagList foreign) throws SecurityException, IllegalArgumentException, IllegalAccessException { List<Tag> values = new ArrayList<>(); int type = foreign.a_(); 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<? extends Tag> 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 */ NBTBase fromNative(Tag foreign) { if (foreign == null) { return null; } if (foreign instanceof CompoundTag) { NBTTagCompound tag = new NBTTagCompound(); for (Map.Entry<String, Tag> 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 boolean supportsWatchdog() { return watchdog != null; } @Override public void tickWatchdog() { watchdog.tick(); } private static class SpigotWatchdog implements Watchdog { @Override public void tick() { WatchdogThread.tick(); } } private static class MojangWatchdog implements Watchdog { private final DedicatedServer server; private final Field tickField; MojangWatchdog(DedicatedServer server) throws NoSuchFieldException { this.server = server; Field tickField = MinecraftServer.class.getDeclaredField("nextTick"); tickField.setAccessible(true); this.tickField = tickField; } @Override public void tick() { try { tickField.set(server, SystemUtils.getMonotonicMillis()); } catch (IllegalAccessException ignored) { } } } private static class NoOpWorldLoadListener implements WorldLoadListener { @Override public void a(ChunkCoordIntPair chunkCoordIntPair) { } @Override public void a(ChunkCoordIntPair chunkCoordIntPair, @Nullable ChunkStatus chunkStatus) { } @Override public void b() { } } }