diff --git a/contrib/nmsblocks/CBXNmsBlock_146.class b/contrib/nmsblocks/CBXNmsBlock_146.class new file mode 100644 index 000000000..0eca14c79 Binary files /dev/null and b/contrib/nmsblocks/CBXNmsBlock_146.class differ diff --git a/contrib/nmsblocks/CBXNmsBlock_146.java b/contrib/nmsblocks/CBXNmsBlock_146.java new file mode 100644 index 000000000..3e1d3693a --- /dev/null +++ b/contrib/nmsblocks/CBXNmsBlock_146.java @@ -0,0 +1,442 @@ +// $Id$ +/* + * This file is a part of WorldEdit. + * Copyright (c) sk89q + * Copyright (c) the 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 + * (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 + * GNU 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 . + */ + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +import net.minecraft.server.v1_4_6.NBTBase; +import net.minecraft.server.v1_4_6.NBTTagByte; +import net.minecraft.server.v1_4_6.NBTTagByteArray; +import net.minecraft.server.v1_4_6.NBTTagCompound; +import net.minecraft.server.v1_4_6.NBTTagDouble; +import net.minecraft.server.v1_4_6.NBTTagEnd; +import net.minecraft.server.v1_4_6.NBTTagFloat; +import net.minecraft.server.v1_4_6.NBTTagInt; +import net.minecraft.server.v1_4_6.NBTTagIntArray; +import net.minecraft.server.v1_4_6.NBTTagList; +import net.minecraft.server.v1_4_6.NBTTagLong; +import net.minecraft.server.v1_4_6.NBTTagShort; +import net.minecraft.server.v1_4_6.NBTTagString; +import net.minecraft.server.v1_4_6.TileEntity; + +import org.bukkit.World; +import org.bukkit.craftbukkit.v1_4_6.CraftWorld; + +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.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.Vector; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.blocks.TileEntityBlock; +import com.sk89q.worldedit.bukkit.BukkitWorld; +import com.sk89q.worldedit.bukkit.NmsBlock; +import com.sk89q.worldedit.data.DataException; +import com.sk89q.worldedit.foundation.Block; + +/** + * This version needs to be built on CraftBukkit 1.4.6 R0.1 or higher + */ +public class CBXNmsBlock_146 extends NmsBlock { + + private static final Logger logger = Logger.getLogger(CBXNmsBlock_146.class.getCanonicalName()); + private static Field compoundMapField; + private static final Field nmsBlock_isTileEntityField; // The field is deobfuscated but the method isn't. No idea why. + private NBTTagCompound nbtData = null; + + static { + Field field; + try { + field = net.minecraft.server.v1_4_6.Block.class.getDeclaredField("isTileEntity"); + field.setAccessible(true); + } catch (Throwable e) { + // logger.severe("Could not find NMS block tile entity field!"); + field = null; + } + nmsBlock_isTileEntityField = field; + } + + public static boolean verify() { + return nmsBlock_isTileEntityField != null; + } + + /** + * Create a new instance with a given type ID, data value, and previous + * {@link TileEntityBlock}-implementing object. + * + * @param type block type ID + * @param data data value + * @param tileEntityBlock tile entity block + */ + public CBXNmsBlock_146(int type, int data, TileEntityBlock tileEntityBlock) { + super(type, data); + + nbtData = (NBTTagCompound) fromNative(tileEntityBlock.getNbtData()); + } + + /** + * Create a new instance with a given type ID, data value, and raw + * {@link NBTTagCompound} copy. + * + * @param type block type ID + * @param data data value + * @param nbtData raw NBT data + */ + public CBXNmsBlock_146(int type, int data, NBTTagCompound nbtData) { + super(type, data); + + this.nbtData = nbtData; + } + + /** + * Build a {@link NBTTagCompound} that has valid coordinates. + * + * @param pt coordinates to set + * @return the tag compound + */ + private NBTTagCompound getNmsData(Vector pt) { + if (nbtData == null) { + return null; + } + + nbtData.set("x", new NBTTagInt("x", pt.getBlockX())); + nbtData.set("y", new NBTTagInt("y", pt.getBlockY())); + nbtData.set("z", new NBTTagInt("z", pt.getBlockZ())); + + return nbtData; + } + + @Override + public boolean hasNbtData() { + return nbtData != null; + } + + @Override + public String getNbtId() { + if (nbtData == null) { + return ""; + } + + return nbtData.getString("id"); + } + + @Override + public CompoundTag getNbtData() { + if (nbtData == null) { + return new CompoundTag(getNbtId(), + new HashMap()); + } + return (CompoundTag) toNative(nbtData); + } + + @Override + public void setNbtData(CompoundTag tag) throws DataException { + if (tag == null) { + this.nbtData = null; + } + this.nbtData = (NBTTagCompound) fromNative(tag); + } + + /** + * Build an instance from the given information. + * + * @param world world to get the block from + * @param position position to get the block at + * @param type type ID of block + * @param data data value of block + * @return the block, or null + */ + public static CBXNmsBlock_146 get(World world, Vector position, int type, int data) { + if (!hasTileEntity(type)) { + return null; + } + + TileEntity te = ((CraftWorld) world).getHandle().getTileEntity( + position.getBlockX(), position.getBlockY(), position.getBlockZ()); + + if (te != null) { + NBTTagCompound tag = new NBTTagCompound(); + te.b(tag); // Load data + return new CBXNmsBlock_146(type, data, tag); + } + + return null; + } + + /** + * Set an instance or a {@link TileEntityBlock} to the given position. + * + * @param world world to set the block in + * @param position position to set the block at + * @param block the block to set + * @return true if tile entity data was copied to the world + */ + public static boolean set(World world, Vector position, BaseBlock block) { + NBTTagCompound data = null; + if (!hasTileEntity(world.getBlockTypeIdAt(position.getBlockX(), position.getBlockY(), position.getBlockZ()))) { + return false; + } + + if (block instanceof CBXNmsBlock_146) { + CBXNmsBlock_146 nmsProxyBlock = (CBXNmsBlock_146) block; + data = nmsProxyBlock.getNmsData(position); + } else if (block instanceof TileEntityBlock) { + CBXNmsBlock_146 nmsProxyBlock = new CBXNmsBlock_146( + block.getId(), block.getData(), block); + data = nmsProxyBlock.getNmsData(position); + } + + if (data != null) { + TileEntity te = ((CraftWorld) world).getHandle().getTileEntity( + position.getBlockX(), position.getBlockY(), position.getBlockZ()); + if (te != null) { + te.a(data); // Load data + return true; + } + } + + return false; + } + + /** + * Tries to set a block 'safely', as in setting the block data to the location, and + * then triggering physics only at the end. + * + * @param world world to set the block in + * @param position position to set the block at + * @param block the block to set + * @param notifyAdjacent true to notify physics and what not + * @return true if block id or data was changed + */ + public static boolean setSafely(BukkitWorld world, Vector position, + Block block, boolean notifyAdjacent) { + + int x = position.getBlockX(); + int y = position.getBlockY(); + int z = position.getBlockZ(); + + CraftWorld craftWorld = ((CraftWorld) world.getWorld()); + + boolean changed = craftWorld.getHandle().setRawTypeIdAndData( + x, y, z, block.getId(), block.getData()); + + if (block instanceof BaseBlock) { + world.copyToWorld(position, (BaseBlock) block); + } + + if (changed) { + if (notifyAdjacent) { + craftWorld.getHandle().update(x, y, z, block.getId()); + } else { + craftWorld.getHandle().notify(x, y, z); + } + } + + return changed; + } + + public static boolean hasTileEntity(int type) { + net.minecraft.server.v1_4_6.Block nmsBlock = getNmsBlock(type); + if (nmsBlock == null) { + return false; + } + + try { + return nmsBlock_isTileEntityField.getBoolean(nmsBlock); // Once we have the field stord, gets are fast + } catch (IllegalAccessException e) { + return false; + } + } + + public static net.minecraft.server.v1_4_6.Block getNmsBlock(int type) { + if (type < 0 || type >= net.minecraft.server.v1_4_6.Block.byId.length) { + return null; + } + return net.minecraft.server.v1_4_6.Block.byId[type]; + } + + /** + * 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") + private static Tag toNative(NBTBase foreign) { + if (foreign == null) { + return null; + } + if (foreign instanceof NBTTagCompound) { + Map values = new HashMap(); + Collection foreignValues = null; + + if (compoundMapField == null) { + try { + // Method name may change! + foreignValues = ((NBTTagCompound) foreign).c(); + } catch (Throwable t) { + try { + logger.warning("WorldEdit: Couldn't get NBTTagCompound.c(), " + + "so we're going to try to get at the 'map' field directly from now on"); + + if (compoundMapField == null) { + compoundMapField = NBTTagCompound.class.getDeclaredField("map"); + compoundMapField.setAccessible(true); + } + } catch (Throwable e) { + // Can't do much beyond this + throw new RuntimeException(e); + } + } + } + + if (compoundMapField != null) { + try { + foreignValues = ((HashMap) compoundMapField.get(foreign)).values(); + } catch (Throwable e) { + // Can't do much beyond this + throw new RuntimeException(e); + } + } + + for (Object obj : foreignValues) { + NBTBase base = (NBTBase) obj; + values.put(base.getName(), toNative(base)); + } + return new CompoundTag(foreign.getName(), values); + } else if (foreign instanceof NBTTagByte) { + return new ByteTag(foreign.getName(), ((NBTTagByte) foreign).data); + } else if (foreign instanceof NBTTagByteArray) { + return new ByteArrayTag(foreign.getName(), + ((NBTTagByteArray) foreign).data); + } else if (foreign instanceof NBTTagDouble) { + return new DoubleTag(foreign.getName(), + ((NBTTagDouble) foreign).data); + } else if (foreign instanceof NBTTagFloat) { + return new FloatTag(foreign.getName(), ((NBTTagFloat) foreign).data); + } else if (foreign instanceof NBTTagInt) { + return new IntTag(foreign.getName(), ((NBTTagInt) foreign).data); + } else if (foreign instanceof NBTTagIntArray) { + return new IntArrayTag(foreign.getName(), + ((NBTTagIntArray) foreign).data); + } else if (foreign instanceof NBTTagList) { + List values = new ArrayList(); + NBTTagList foreignList = (NBTTagList) foreign; + int type = NBTConstants.TYPE_BYTE; + for (int i = 0; i < foreignList.size(); i++) { + NBTBase foreignTag = foreignList.get(i); + values.add(toNative(foreignTag)); + type = foreignTag.getTypeId(); + } + Class cls = NBTConstants.getClassFromType(type); + return new ListTag(foreign.getName(), cls, values); + } else if (foreign instanceof NBTTagLong) { + return new LongTag(foreign.getName(), ((NBTTagLong) foreign).data); + } else if (foreign instanceof NBTTagShort) { + return new ShortTag(foreign.getName(), ((NBTTagShort) foreign).data); + } else if (foreign instanceof NBTTagString) { + return new StringTag(foreign.getName(), + ((NBTTagString) foreign).data); + } else if (foreign instanceof NBTTagEnd) { + return new EndTag(); + } else { + throw new IllegalArgumentException("Don't know how to make native " + + foreign.getClass().getCanonicalName()); + } + } + + /** + * Converts a WorldEdit-native NBT structure to a NMS structure. + * + * @param foreign structure to convert + * @return non-native structure + */ + private static NBTBase fromNative(Tag foreign) { + if (foreign == null) { + return null; + } + if (foreign instanceof CompoundTag) { + NBTTagCompound tag = new NBTTagCompound(foreign.getName()); + 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(foreign.getName(), + ((ByteTag) foreign).getValue()); + } else if (foreign instanceof ByteArrayTag) { + return new NBTTagByteArray(foreign.getName(), + ((ByteArrayTag) foreign).getValue()); + } else if (foreign instanceof DoubleTag) { + return new NBTTagDouble(foreign.getName(), + ((DoubleTag) foreign).getValue()); + } else if (foreign instanceof FloatTag) { + return new NBTTagFloat(foreign.getName(), + ((FloatTag) foreign).getValue()); + } else if (foreign instanceof IntTag) { + return new NBTTagInt(foreign.getName(), + ((IntTag) foreign).getValue()); + } else if (foreign instanceof IntArrayTag) { + return new NBTTagIntArray(foreign.getName(), + ((IntArrayTag) foreign).getValue()); + } else if (foreign instanceof ListTag) { + NBTTagList tag = new NBTTagList(foreign.getName()); + ListTag foreignList = (ListTag) foreign; + for (Tag t : foreignList.getValue()) { + tag.add(fromNative(t)); + } + return tag; + } else if (foreign instanceof LongTag) { + return new NBTTagLong(foreign.getName(), + ((LongTag) foreign).getValue()); + } else if (foreign instanceof ShortTag) { + return new NBTTagShort(foreign.getName(), + ((ShortTag) foreign).getValue()); + } else if (foreign instanceof StringTag) { + return new NBTTagString(foreign.getName(), + ((StringTag) foreign).getValue()); + } else if (foreign instanceof EndTag) { + return new NBTTagEnd(); + } else { + throw new IllegalArgumentException("Don't know how to make NMS " + + foreign.getClass().getCanonicalName()); + } + } + + public static boolean isValidBlockType(int type) throws NoClassDefFoundError { + return type == 0 || (type >= 1 && type < net.minecraft.server.v1_4_6.Block.byId.length + && net.minecraft.server.v1_4_6.Block.byId[type] != null); + } +} diff --git a/contrib/nmsblocks/MCPCPXNmsBlock.class b/contrib/nmsblocks/MCPCPXNmsBlock.class new file mode 100644 index 000000000..95ba91ea0 Binary files /dev/null and b/contrib/nmsblocks/MCPCPXNmsBlock.class differ diff --git a/contrib/nmsblocks/MCPCPXNmsBlock.java b/contrib/nmsblocks/MCPCPXNmsBlock.java new file mode 100644 index 000000000..6197a942a --- /dev/null +++ b/contrib/nmsblocks/MCPCPXNmsBlock.java @@ -0,0 +1,449 @@ +// $Id$ +/* + * This file is a part of WorldEdit. + * Copyright (c) sk89q + * Copyright (c) the 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 + * (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 + * GNU 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 . + */ + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +import net.minecraft.server.v1_4_R1.NBTBase; +import net.minecraft.server.v1_4_R1.NBTTagByte; +import net.minecraft.server.v1_4_R1.NBTTagByteArray; +import net.minecraft.server.v1_4_R1.NBTTagCompound; +import net.minecraft.server.v1_4_R1.NBTTagDouble; +import net.minecraft.server.v1_4_R1.NBTTagEnd; +import net.minecraft.server.v1_4_R1.NBTTagFloat; +import net.minecraft.server.v1_4_R1.NBTTagInt; +import net.minecraft.server.v1_4_R1.NBTTagIntArray; +import net.minecraft.server.v1_4_R1.NBTTagList; +import net.minecraft.server.v1_4_R1.NBTTagLong; +import net.minecraft.server.v1_4_R1.NBTTagShort; +import net.minecraft.server.v1_4_R1.NBTTagString; +import net.minecraft.server.v1_4_R1.TileEntity; + +import org.bukkit.World; +import org.bukkit.craftbukkit.v1_4_R1.CraftWorld; + +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.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.Vector; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.blocks.TileEntityBlock; +import com.sk89q.worldedit.bukkit.BukkitWorld; +import com.sk89q.worldedit.bukkit.NmsBlock; +import com.sk89q.worldedit.data.DataException; +import com.sk89q.worldedit.foundation.Block; + +/** + * This class needs to be obfuscated and compiled in a forge environment + */ +public class MCPCPXNmsBlock extends NmsBlock { + + private static final Logger logger = Logger.getLogger(MCPCPXNmsBlock.class.getCanonicalName()); + private static Field compoundMapField; + private static final Field nmsBlock_isTileEntityField; // The field is deobfuscated but the method isn't. No idea why. + private NBTTagCompound nbtData = null; + + static { + Field field; + try { + // this part is important, has to be updated manually whenever it changes + field = net.minecraft.server.v1_4_R1.Block.class.getDeclaredField("cs"); // isTileEntity or isContainerBlock + field.setAccessible(true); + } catch (Throwable e) { + // logger.severe("Could not find NMS block tile entity field!"); + field = null; + } + nmsBlock_isTileEntityField = field; + } + + public static boolean verify() { + try { + Class.forName("org.bukkit.craftbukkit.v1_4_R1.CraftWorld"); + } catch (Throwable e) { + return false; + } + return nmsBlock_isTileEntityField != null; + } + + /** + * Create a new instance with a given type ID, data value, and previous + * {@link TileEntityBlock}-implementing object. + * + * @param type block type ID + * @param data data value + * @param tileEntityBlock tile entity block + */ + public MCPCPXNmsBlock(int type, int data, TileEntityBlock tileEntityBlock) { + super(type, data); + + nbtData = (NBTTagCompound) fromNative(tileEntityBlock.getNbtData()); + } + + /** + * Create a new instance with a given type ID, data value, and raw + * {@link NBTTagCompound} copy. + * + * @param type block type ID + * @param data data value + * @param nbtData raw NBT data + */ + public MCPCPXNmsBlock(int type, int data, NBTTagCompound nbtData) { + super(type, data); + + this.nbtData = nbtData; + } + + /** + * Build a {@link NBTTagCompound} that has valid coordinates. + * + * @param pt coordinates to set + * @return the tag compound + */ + private NBTTagCompound getNmsData(Vector pt) { + if (nbtData == null) { + return null; + } + + nbtData.set("x", new NBTTagInt("x", pt.getBlockX())); + nbtData.set("y", new NBTTagInt("y", pt.getBlockY())); + nbtData.set("z", new NBTTagInt("z", pt.getBlockZ())); + + return nbtData; + } + + @Override + public boolean hasNbtData() { + return nbtData != null; + } + + @Override + public String getNbtId() { + if (nbtData == null) { + return ""; + } + + return nbtData.getString("id"); + } + + @Override + public CompoundTag getNbtData() { + if (nbtData == null) { + return new CompoundTag(getNbtId(), + new HashMap()); + } + return (CompoundTag) toNative(nbtData); + } + + @Override + public void setNbtData(CompoundTag tag) throws DataException { + if (tag == null) { + this.nbtData = null; + } + this.nbtData = (NBTTagCompound) fromNative(tag); + } + + /** + * Build an instance from the given information. + * + * @param world world to get the block from + * @param position position to get the block at + * @param type type ID of block + * @param data data value of block + * @return the block, or null + */ + public static MCPCPXNmsBlock get(World world, Vector position, int type, int data) { + if (!hasTileEntity(type)) { + return null; + } + + TileEntity te = ((CraftWorld) world).getHandle().getTileEntity( + position.getBlockX(), position.getBlockY(), position.getBlockZ()); + + if (te != null) { + NBTTagCompound tag = new NBTTagCompound(); + te.b(tag); // Load data + return new MCPCPXNmsBlock(type, data, tag); + } + + return null; + } + + /** + * Set an instance or a {@link TileEntityBlock} to the given position. + * + * @param world world to set the block in + * @param position position to set the block at + * @param block the block to set + * @return true if tile entity data was copied to the world + */ + public static boolean set(World world, Vector position, BaseBlock block) { + NBTTagCompound data = null; + if (!hasTileEntity(world.getBlockTypeIdAt(position.getBlockX(), position.getBlockY(), position.getBlockZ()))) { + return false; + } + + if (block instanceof MCPCPXNmsBlock) { + MCPCPXNmsBlock nmsProxyBlock = (MCPCPXNmsBlock) block; + data = nmsProxyBlock.getNmsData(position); + } else if (block instanceof TileEntityBlock) { + MCPCPXNmsBlock nmsProxyBlock = new MCPCPXNmsBlock( + block.getId(), block.getData(), block); + data = nmsProxyBlock.getNmsData(position); + } + + if (data != null) { + TileEntity te = ((CraftWorld) world).getHandle().getTileEntity( + position.getBlockX(), position.getBlockY(), position.getBlockZ()); + if (te != null) { + te.a(data); // Load data + return true; + } + } + + return false; + } + + /** + * Tries to set a block 'safely', as in setting the block data to the location, and + * then triggering physics only at the end. + * + * @param world world to set the block in + * @param position position to set the block at + * @param block the block to set + * @param notifyAdjacent true to notify physics and what not + * @return true if block id or data was changed + */ + public static boolean setSafely(BukkitWorld world, Vector position, + Block block, boolean notifyAdjacent) { + + int x = position.getBlockX(); + int y = position.getBlockY(); + int z = position.getBlockZ(); + + CraftWorld craftWorld = ((CraftWorld) world.getWorld()); + + boolean changed = craftWorld.getHandle().setRawTypeIdAndData( + x, y, z, block.getId(), block.getData()); + + if (block instanceof BaseBlock) { + world.copyToWorld(position, (BaseBlock) block); + } + + if (changed) { + if (notifyAdjacent) { + craftWorld.getHandle().update(x, y, z, block.getId()); + } else { + craftWorld.getHandle().notify(x, y, z); + } + } + + return changed; + } + + public static boolean hasTileEntity(int type) { + net.minecraft.server.v1_4_R1.Block nmsBlock = getNmsBlock(type); + if (nmsBlock == null) { + return false; + } + + try { + return nmsBlock_isTileEntityField.getBoolean(nmsBlock); // Once we have the field stord, gets are fast + } catch (IllegalAccessException e) { + return false; + } + } + + public static net.minecraft.server.v1_4_R1.Block getNmsBlock(int type) { + if (type < 0 || type >= net.minecraft.server.v1_4_R1.Block.byId.length) { + return null; + } + return net.minecraft.server.v1_4_R1.Block.byId[type]; + } + + /** + * 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") + private static Tag toNative(NBTBase foreign) { + if (foreign == null) { + return null; + } + if (foreign instanceof NBTTagCompound) { + Map values = new HashMap(); + Collection foreignValues = null; + + if (compoundMapField == null) { + try { + // Method name may change! + foreignValues = ((NBTTagCompound) foreign).c(); + } catch (Throwable t) { + try { + // this shouldn't happen here since we are reobfuscating to the correct name + logger.warning("WorldEdit: Couldn't get NBTTagCompound.getLists(), " + + "so we're going to try to get at the 'map' field directly from now on"); + + if (compoundMapField == null) { + compoundMapField = NBTTagCompound.class.getDeclaredField("map"); + compoundMapField.setAccessible(true); + } + } catch (Throwable e) { + // Can't do much beyond this + throw new RuntimeException(e); + } + } + } + + if (compoundMapField != null) { + try { + foreignValues = ((HashMap) compoundMapField.get(foreign)).values(); + } catch (Throwable e) { + // Can't do much beyond this + throw new RuntimeException(e); + } + } + + for (Object obj : foreignValues) { + NBTBase base = (NBTBase) obj; + values.put(base.getName(), toNative(base)); + } + return new CompoundTag(foreign.getName(), values); + } else if (foreign instanceof NBTTagByte) { + return new ByteTag(foreign.getName(), ((NBTTagByte) foreign).data); + } else if (foreign instanceof NBTTagByteArray) { + return new ByteArrayTag(foreign.getName(), + ((NBTTagByteArray) foreign).data); + } else if (foreign instanceof NBTTagDouble) { + return new DoubleTag(foreign.getName(), + ((NBTTagDouble) foreign).data); + } else if (foreign instanceof NBTTagFloat) { + return new FloatTag(foreign.getName(), ((NBTTagFloat) foreign).data); + } else if (foreign instanceof NBTTagInt) { + return new IntTag(foreign.getName(), ((NBTTagInt) foreign).data); + } else if (foreign instanceof NBTTagIntArray) { + return new IntArrayTag(foreign.getName(), + ((NBTTagIntArray) foreign).data); + } else if (foreign instanceof NBTTagList) { + List values = new ArrayList(); + NBTTagList foreignList = (NBTTagList) foreign; + int type = NBTConstants.TYPE_BYTE; + for (int i = 0; i < foreignList.size(); i++) { + NBTBase foreignTag = foreignList.get(i); + values.add(toNative(foreignTag)); + type = foreignTag.getTypeId(); + } + Class cls = NBTConstants.getClassFromType(type); + return new ListTag(foreign.getName(), cls, values); + } else if (foreign instanceof NBTTagLong) { + return new LongTag(foreign.getName(), ((NBTTagLong) foreign).data); + } else if (foreign instanceof NBTTagShort) { + return new ShortTag(foreign.getName(), ((NBTTagShort) foreign).data); + } else if (foreign instanceof NBTTagString) { + return new StringTag(foreign.getName(), + ((NBTTagString) foreign).data); + } else if (foreign instanceof NBTTagEnd) { + return new EndTag(); + } else { + throw new IllegalArgumentException("Don't know how to make native " + + foreign.getClass().getCanonicalName()); + } + } + + /** + * Converts a WorldEdit-native NBT structure to a NMS structure. + * + * @param foreign structure to convert + * @return non-native structure + */ + private static NBTBase fromNative(Tag foreign) { + if (foreign == null) { + return null; + } + if (foreign instanceof CompoundTag) { + NBTTagCompound tag = new NBTTagCompound(foreign.getName()); + 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(foreign.getName(), + ((ByteTag) foreign).getValue()); + } else if (foreign instanceof ByteArrayTag) { + return new NBTTagByteArray(foreign.getName(), + ((ByteArrayTag) foreign).getValue()); + } else if (foreign instanceof DoubleTag) { + return new NBTTagDouble(foreign.getName(), + ((DoubleTag) foreign).getValue()); + } else if (foreign instanceof FloatTag) { + return new NBTTagFloat(foreign.getName(), + ((FloatTag) foreign).getValue()); + } else if (foreign instanceof IntTag) { + return new NBTTagInt(foreign.getName(), + ((IntTag) foreign).getValue()); + } else if (foreign instanceof IntArrayTag) { + return new NBTTagIntArray(foreign.getName(), + ((IntArrayTag) foreign).getValue()); + } else if (foreign instanceof ListTag) { + NBTTagList tag = new NBTTagList(foreign.getName()); + ListTag foreignList = (ListTag) foreign; + for (Tag t : foreignList.getValue()) { + tag.add(fromNative(t)); + } + return tag; + } else if (foreign instanceof LongTag) { + return new NBTTagLong(foreign.getName(), + ((LongTag) foreign).getValue()); + } else if (foreign instanceof ShortTag) { + return new NBTTagShort(foreign.getName(), + ((ShortTag) foreign).getValue()); + } else if (foreign instanceof StringTag) { + return new NBTTagString(foreign.getName(), + ((StringTag) foreign).getValue()); + } else if (foreign instanceof EndTag) { + return new NBTTagEnd(); + } else { + throw new IllegalArgumentException("Don't know how to make NMS " + + foreign.getClass().getCanonicalName()); + } + } + + public static boolean isValidBlockType(int type) throws NoClassDefFoundError { + return type == 0 || (type >= 1 && type < net.minecraft.server.v1_4_R1.Block.byId.length + && net.minecraft.server.v1_4_R1.Block.byId[type] != null); + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 7df8fdfd4..d6426009b 100644 --- a/pom.xml +++ b/pom.xml @@ -90,7 +90,7 @@ org.bukkit craftbukkit - 1.4.6-R0.1 + 1.4.7-R0.1 compile true diff --git a/src/main/assembly/default.xml b/src/main/assembly/default.xml index 3ba11e550..76d6de02e 100644 --- a/src/main/assembly/default.xml +++ b/src/main/assembly/default.xml @@ -28,6 +28,7 @@ LICENSE.txt CHANGELOG.txt contrib/craftscripts/* + contrib/nmsblocks/*.class diff --git a/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java b/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java index 00c1243b8..bb9cd30eb 100644 --- a/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java +++ b/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java @@ -19,6 +19,11 @@ package com.sk89q.worldedit.bukkit; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.net.URL; import java.util.ArrayList; import java.util.EnumMap; import java.util.HashMap; @@ -30,6 +35,7 @@ import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; +import org.bukkit.Bukkit; import org.bukkit.Effect; import org.bukkit.Location; import org.bukkit.Material; @@ -67,6 +73,7 @@ import org.bukkit.entity.Villager; import org.bukkit.inventory.DoubleChestInventory; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; import com.sk89q.worldedit.BiomeType; import com.sk89q.worldedit.BlockVector2D; @@ -92,18 +99,128 @@ import com.sk89q.worldedit.util.TreeGenerator; public class BukkitWorld extends LocalWorld { - private static final Logger logger = Logger.getLogger(BukkitWorld.class.getCanonicalName()); + private static final Logger logger = WorldEdit.logger; private World world; private boolean skipNmsAccess = false; private boolean skipNmsSafeSet = false; private boolean skipNmsValidBlockCheck = false; + /* + * holder for the nmsblock class that we should use + */ + private static Class nmsBlockType; + private static Method nmsSetMethod; + private static Method nmsValidBlockMethod; + private static Method nmsGetMethod; + private static Method nmsSetSafeMethod; + /** * Construct the object. * @param world */ public BukkitWorld(World world) { this.world = world; + + // check if we have a class we can use for nms access + if (nmsBlockType != null) return; + Plugin plugin = Bukkit.getPluginManager().getPlugin("WorldEdit"); + if (!(plugin instanceof WorldEditPlugin)) return; // hopefully never happens + WorldEditPlugin wePlugin = ((WorldEditPlugin) plugin); + File nmsBlocksDir = new File(wePlugin.getDataFolder() + File.separator + "nmsblocks" + File.separator); + if (nmsBlocksDir.listFiles() == null) { // no files to use + skipNmsAccess = true; skipNmsSafeSet = true; skipNmsValidBlockCheck = true; + return; + } + try { + NmsBlockClassLoader loader = new NmsBlockClassLoader(BukkitWorld.class.getClassLoader(), nmsBlocksDir); + String filename; + for (File f : nmsBlocksDir.listFiles()) { + if (!f.isFile()) continue; + filename = f.getName(); + Class testBlock = loader.loadClass("CL-NMS" + filename); + filename = filename.replaceFirst(".class$", ""); // get rid of extension + if (NmsBlock.class.isAssignableFrom(testBlock)) { + // got a NmsBlock, test it now + Class nmsClass = (Class) testBlock; + boolean canUse = false; + try { + canUse = (Boolean) nmsClass.getMethod("verify", null).invoke(null, null); + } catch (Throwable e) { + continue; + } + if (!canUse) continue; // not for this server + nmsBlockType = nmsClass; + nmsSetMethod = nmsBlockType.getMethod("set", World.class, Vector.class, BaseBlock.class); + nmsValidBlockMethod = nmsBlockType.getMethod("isValidBlockType", int.class); + nmsGetMethod = nmsBlockType.getMethod("get", World.class, Vector.class, int.class, int.class); + nmsSetSafeMethod = nmsBlockType.getMethod("setSafely", + BukkitWorld.class, Vector.class, com.sk89q.worldedit.foundation.Block.class, boolean.class); + // phew + break; + } + } + if (nmsBlockType != null) { + // logger.info("Found nms block class, using: " + nmsBlockType); + } else { + // try our default + try { + nmsBlockType = (Class) Class.forName("com.sk89q.worldedit.bukkit.DefaultNmsBlock"); + boolean canUse = (Boolean) nmsBlockType.getMethod("verify", null).invoke(null, null); + if (canUse) { + nmsSetMethod = nmsBlockType.getMethod("set", World.class, Vector.class, BaseBlock.class); + nmsValidBlockMethod = nmsBlockType.getMethod("isValidBlockType", int.class); + nmsGetMethod = nmsBlockType.getMethod("get", World.class, Vector.class, int.class, int.class); + nmsSetSafeMethod = nmsBlockType.getMethod("setSafely", + BukkitWorld.class, Vector.class, com.sk89q.worldedit.foundation.Block.class, boolean.class); + logger.info("[WorldEdit] Using inbuilt NmsBlock for this version of WorldEdit."); + } + } catch (Throwable e) { + // OMG DEVS WAI U NO SUPPORT SERVER + skipNmsAccess = true; skipNmsSafeSet = true; skipNmsValidBlockCheck = true; + logger.warning("[WorldEdit] No compatible nms block class found."); + } + } + } catch (Throwable e) { + logger.warning("[WorldEdit] Unable to load NmsBlock classes, make sure they are installed correctly."); + e.printStackTrace(); + skipNmsAccess = true; skipNmsSafeSet = true; skipNmsValidBlockCheck = true; + } + } + + private class NmsBlockClassLoader extends ClassLoader { + public File searchDir; + public NmsBlockClassLoader(ClassLoader parent, File searchDir) { + super(parent); + this.searchDir = searchDir; + } + + @Override + public Class loadClass(String name) throws ClassNotFoundException { + if (!name.startsWith("CL-NMS")) { + return super.loadClass(name); + } else { + name = name.replace("CL-NMS", ""); // hacky lol + } + try { + URL url = new File(searchDir, name).toURI().toURL(); + InputStream input = url.openConnection().getInputStream(); + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + + int data = input.read(); + while (data != -1) { + buffer.write(data); + data = input.read(); + } + input.close(); + + byte[] classData = buffer.toByteArray(); + + return defineClass(name.replaceFirst(".class$", ""), classData, 0, classData.length); + } catch (Throwable e) { + e.printStackTrace(); + throw new ClassNotFoundException(); + } + } } /** @@ -455,7 +572,7 @@ public class BukkitWorld extends LocalWorld { if (!skipNmsAccess) { try { - return NmsBlock.set(world, pt, block); + return (Boolean) nmsSetMethod.invoke(null, world, pt, block); } catch (Throwable t) { logger.log(Level.WARNING, "WorldEdit: Failed to do NMS access for direct NBT data copy", t); skipNmsAccess = true; @@ -1064,7 +1181,7 @@ public class BukkitWorld extends LocalWorld { public boolean isValidBlockType(int type) { if (!skipNmsValidBlockCheck) { try { - return NmsBlock.isValidBlockType(type); + return (Boolean) nmsValidBlockMethod.invoke(null, type); } catch (Throwable e) { skipNmsValidBlockCheck = true; } @@ -1180,7 +1297,8 @@ public class BukkitWorld extends LocalWorld { default: if (!skipNmsAccess) { try { - NmsBlock block = NmsBlock.get(world, pt, type, data); + NmsBlock block = null; + block = (NmsBlock) nmsGetMethod.invoke(null, getWorld(), pt, type, data); if (block != null) { return block; } @@ -1199,10 +1317,9 @@ public class BukkitWorld extends LocalWorld { public boolean setBlock(Vector pt, com.sk89q.worldedit.foundation.Block block, boolean notifyAdjacent) { if (!skipNmsSafeSet) { try { - return NmsBlock.setSafely(this, pt, block, notifyAdjacent); + return (Boolean) nmsSetSafeMethod.invoke(null, this, pt, block, notifyAdjacent); } catch (Throwable t) { - logger.log(Level.WARNING, - "WorldEdit: Failed to do NMS safe block set", t); + logger.log(Level.WARNING, "WorldEdit: Failed to do NMS safe block set", t); skipNmsSafeSet = true; } } diff --git a/src/main/java/com/sk89q/worldedit/bukkit/DefaultNmsBlock.java b/src/main/java/com/sk89q/worldedit/bukkit/DefaultNmsBlock.java new file mode 100644 index 000000000..f1f451b04 --- /dev/null +++ b/src/main/java/com/sk89q/worldedit/bukkit/DefaultNmsBlock.java @@ -0,0 +1,446 @@ +package com.sk89q.worldedit.bukkit; +// $Id$ +/* + * This file is a part of WorldEdit. + * Copyright (c) sk89q + * Copyright (c) the 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 + * (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 + * GNU 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 . + */ + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +import net.minecraft.server.v1_4_R1.NBTBase; +import net.minecraft.server.v1_4_R1.NBTTagByte; +import net.minecraft.server.v1_4_R1.NBTTagByteArray; +import net.minecraft.server.v1_4_R1.NBTTagCompound; +import net.minecraft.server.v1_4_R1.NBTTagDouble; +import net.minecraft.server.v1_4_R1.NBTTagEnd; +import net.minecraft.server.v1_4_R1.NBTTagFloat; +import net.minecraft.server.v1_4_R1.NBTTagInt; +import net.minecraft.server.v1_4_R1.NBTTagIntArray; +import net.minecraft.server.v1_4_R1.NBTTagList; +import net.minecraft.server.v1_4_R1.NBTTagLong; +import net.minecraft.server.v1_4_R1.NBTTagShort; +import net.minecraft.server.v1_4_R1.NBTTagString; +import net.minecraft.server.v1_4_R1.TileEntity; + +import org.bukkit.World; +import org.bukkit.craftbukkit.v1_4_R1.CraftWorld; + +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.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.Vector; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.blocks.TileEntityBlock; +import com.sk89q.worldedit.data.DataException; +import com.sk89q.worldedit.foundation.Block; + +/** + * A blind handler of blocks with TileEntity data that directly access Minecraft's + * classes through CraftBukkit. + *

+ * Usage of this class may break terribly in the future, and therefore usage should + * be trapped in a handler for {@link Throwable}. + */ +public class DefaultNmsBlock extends NmsBlock { + + private static final Logger logger = WorldEdit.logger; + private static Field compoundMapField; + private static final Field nmsBlock_isTileEntityField; // The field is deobfuscated but the method isn't. No idea why. + private NBTTagCompound nbtData = null; + + static { + Field field; + try { + field = net.minecraft.server.v1_4_R1.Block.class.getDeclaredField("isTileEntity"); + field.setAccessible(true); + } catch (NoSuchFieldException e) { + // logger.severe("Could not find NMS block tile entity field!"); + field = null; + } + nmsBlock_isTileEntityField = field; + } + + public static boolean verify() { + return nmsBlock_isTileEntityField != null; + } + + /** + * Create a new instance with a given type ID, data value, and previous + * {@link TileEntityBlock}-implementing object. + * + * @param type block type ID + * @param data data value + * @param tileEntityBlock tile entity block + */ + public DefaultNmsBlock(int type, int data, TileEntityBlock tileEntityBlock) { + super(type, data); + + nbtData = (NBTTagCompound) fromNative(tileEntityBlock.getNbtData()); + } + + /** + * Create a new instance with a given type ID, data value, and raw + * {@link NBTTagCompound} copy. + * + * @param type block type ID + * @param data data value + * @param nbtData raw NBT data + */ + public DefaultNmsBlock(int type, int data, NBTTagCompound nbtData) { + super(type, data); + + this.nbtData = nbtData; + } + + /** + * Build a {@link NBTTagCompound} that has valid coordinates. + * + * @param pt coordinates to set + * @return the tag compound + */ + private NBTTagCompound getNmsData(Vector pt) { + if (nbtData == null) { + return null; + } + + nbtData.set("x", new NBTTagInt("x", pt.getBlockX())); + nbtData.set("y", new NBTTagInt("y", pt.getBlockY())); + nbtData.set("z", new NBTTagInt("z", pt.getBlockZ())); + + return nbtData; + } + + @Override + public boolean hasNbtData() { + return nbtData != null; + } + + @Override + public String getNbtId() { + if (nbtData == null) { + return ""; + } + + return nbtData.getString("id"); + } + + @Override + public CompoundTag getNbtData() { + if (nbtData == null) { + return new CompoundTag(getNbtId(), + new HashMap()); + } + return (CompoundTag) toNative(nbtData); + } + + @Override + public void setNbtData(CompoundTag tag) throws DataException { + if (tag == null) { + this.nbtData = null; + } + this.nbtData = (NBTTagCompound) fromNative(tag); + } + + /** + * Build an instance from the given information. + * + * @param world world to get the block from + * @param position position to get the block at + * @param type type ID of block + * @param data data value of block + * @return the block, or null + */ + public static DefaultNmsBlock get(World world, Vector position, int type, int data) { + if (!hasTileEntity(type)) { + return null; + } + + TileEntity te = ((CraftWorld) world).getHandle().getTileEntity( + position.getBlockX(), position.getBlockY(), position.getBlockZ()); + + if (te != null) { + NBTTagCompound tag = new NBTTagCompound(); + te.b(tag); // Load data + return new DefaultNmsBlock(type, data, tag); + } + + return null; + } + + /** + * Set an instance or a {@link TileEntityBlock} to the given position. + * + * @param world world to set the block in + * @param position position to set the block at + * @param block the block to set + * @return true if tile entity data was copied to the world + */ + public static boolean set(World world, Vector position, BaseBlock block) { + NBTTagCompound data = null; + if (!hasTileEntity(world.getBlockTypeIdAt(position.getBlockX(), position.getBlockY(), position.getBlockZ()))) { + return false; + } + + if (block instanceof DefaultNmsBlock) { + DefaultNmsBlock nmsProxyBlock = (DefaultNmsBlock) block; + data = nmsProxyBlock.getNmsData(position); + } else if (block instanceof TileEntityBlock) { + DefaultNmsBlock nmsProxyBlock = new DefaultNmsBlock( + block.getId(), block.getData(), block); + data = nmsProxyBlock.getNmsData(position); + } + + if (data != null) { + TileEntity te = ((CraftWorld) world).getHandle().getTileEntity( + position.getBlockX(), position.getBlockY(), position.getBlockZ()); + if (te != null) { + te.a(data); // Load data + return true; + } + } + + return false; + } + + /** + * Tries to set a block 'safely', as in setting the block data to the location, and + * then triggering physics only at the end. + * + * @param world world to set the block in + * @param position position to set the block at + * @param block the block to set + * @param notifyAdjacent true to notify physics and what not + * @return true if block id or data was changed + */ + public static boolean setSafely(BukkitWorld world, Vector position, + Block block, boolean notifyAdjacent) { + + int x = position.getBlockX(); + int y = position.getBlockY(); + int z = position.getBlockZ(); + + CraftWorld craftWorld = ((CraftWorld) world.getWorld()); + + boolean changed = craftWorld.getHandle().setRawTypeIdAndData( + x, y, z, block.getId(), block.getData()); + + if (block instanceof BaseBlock) { + world.copyToWorld(position, (BaseBlock) block); + } + + if (changed) { + if (notifyAdjacent) { + craftWorld.getHandle().update(x, y, z, block.getId()); + } else { + craftWorld.getHandle().notify(x, y, z); + } + } + + return changed; + } + + public static boolean hasTileEntity(int type) { + net.minecraft.server.v1_4_R1.Block nmsBlock = getNmsBlock(type); + if (nmsBlock == null) { + return false; + } + + try { + return nmsBlock_isTileEntityField.getBoolean(nmsBlock); // Once we have the field stord, gets are fast + } catch (IllegalAccessException e) { + return false; + } + } + + public static net.minecraft.server.v1_4_R1.Block getNmsBlock(int type) { + if (type < 0 || type >= net.minecraft.server.v1_4_R1.Block.byId.length) { + return null; + } + return net.minecraft.server.v1_4_R1.Block.byId[type]; + } + + /** + * 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") + private static Tag toNative(NBTBase foreign) { + if (foreign == null) { + return null; + } + if (foreign instanceof NBTTagCompound) { + Map values = new HashMap(); + Collection foreignValues = null; + + if (compoundMapField == null) { + try { + // Method name may change! + foreignValues = ((NBTTagCompound) foreign).c(); + } catch (Throwable t) { + try { + logger.warning("WorldEdit: Couldn't get NBTTagCompound.c(), " + + "so we're going to try to get at the 'map' field directly from now on"); + + if (compoundMapField == null) { + compoundMapField = NBTTagCompound.class.getDeclaredField("map"); + compoundMapField.setAccessible(true); + } + } catch (Throwable e) { + // Can't do much beyond this + throw new RuntimeException(e); + } + } + } + + if (compoundMapField != null) { + try { + foreignValues = ((HashMap) compoundMapField.get(foreign)).values(); + } catch (Throwable e) { + // Can't do much beyond this + throw new RuntimeException(e); + } + } + + for (Object obj : foreignValues) { + NBTBase base = (NBTBase) obj; + values.put(base.getName(), toNative(base)); + } + return new CompoundTag(foreign.getName(), values); + } else if (foreign instanceof NBTTagByte) { + return new ByteTag(foreign.getName(), ((NBTTagByte) foreign).data); + } else if (foreign instanceof NBTTagByteArray) { + return new ByteArrayTag(foreign.getName(), + ((NBTTagByteArray) foreign).data); + } else if (foreign instanceof NBTTagDouble) { + return new DoubleTag(foreign.getName(), + ((NBTTagDouble) foreign).data); + } else if (foreign instanceof NBTTagFloat) { + return new FloatTag(foreign.getName(), ((NBTTagFloat) foreign).data); + } else if (foreign instanceof NBTTagInt) { + return new IntTag(foreign.getName(), ((NBTTagInt) foreign).data); + } else if (foreign instanceof NBTTagIntArray) { + return new IntArrayTag(foreign.getName(), + ((NBTTagIntArray) foreign).data); + } else if (foreign instanceof NBTTagList) { + List values = new ArrayList(); + NBTTagList foreignList = (NBTTagList) foreign; + int type = NBTConstants.TYPE_BYTE; + for (int i = 0; i < foreignList.size(); i++) { + NBTBase foreignTag = foreignList.get(i); + values.add(toNative(foreignTag)); + type = foreignTag.getTypeId(); + } + Class cls = NBTConstants.getClassFromType(type); + return new ListTag(foreign.getName(), cls, values); + } else if (foreign instanceof NBTTagLong) { + return new LongTag(foreign.getName(), ((NBTTagLong) foreign).data); + } else if (foreign instanceof NBTTagShort) { + return new ShortTag(foreign.getName(), ((NBTTagShort) foreign).data); + } else if (foreign instanceof NBTTagString) { + return new StringTag(foreign.getName(), + ((NBTTagString) foreign).data); + } else if (foreign instanceof NBTTagEnd) { + return new EndTag(); + } else { + throw new IllegalArgumentException("Don't know how to make native " + + foreign.getClass().getCanonicalName()); + } + } + + /** + * Converts a WorldEdit-native NBT structure to a NMS structure. + * + * @param foreign structure to convert + * @return non-native structure + */ + private static NBTBase fromNative(Tag foreign) { + if (foreign == null) { + return null; + } + if (foreign instanceof CompoundTag) { + NBTTagCompound tag = new NBTTagCompound(foreign.getName()); + 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(foreign.getName(), + ((ByteTag) foreign).getValue()); + } else if (foreign instanceof ByteArrayTag) { + return new NBTTagByteArray(foreign.getName(), + ((ByteArrayTag) foreign).getValue()); + } else if (foreign instanceof DoubleTag) { + return new NBTTagDouble(foreign.getName(), + ((DoubleTag) foreign).getValue()); + } else if (foreign instanceof FloatTag) { + return new NBTTagFloat(foreign.getName(), + ((FloatTag) foreign).getValue()); + } else if (foreign instanceof IntTag) { + return new NBTTagInt(foreign.getName(), + ((IntTag) foreign).getValue()); + } else if (foreign instanceof IntArrayTag) { + return new NBTTagIntArray(foreign.getName(), + ((IntArrayTag) foreign).getValue()); + } else if (foreign instanceof ListTag) { + NBTTagList tag = new NBTTagList(foreign.getName()); + ListTag foreignList = (ListTag) foreign; + for (Tag t : foreignList.getValue()) { + tag.add(fromNative(t)); + } + return tag; + } else if (foreign instanceof LongTag) { + return new NBTTagLong(foreign.getName(), + ((LongTag) foreign).getValue()); + } else if (foreign instanceof ShortTag) { + return new NBTTagShort(foreign.getName(), + ((ShortTag) foreign).getValue()); + } else if (foreign instanceof StringTag) { + return new NBTTagString(foreign.getName(), + ((StringTag) foreign).getValue()); + } else if (foreign instanceof EndTag) { + return new NBTTagEnd(); + } else { + throw new IllegalArgumentException("Don't know how to make NMS " + + foreign.getClass().getCanonicalName()); + } + } + + public static boolean isValidBlockType(int type) throws NoClassDefFoundError { + return type == 0 || (type >= 1 && type < net.minecraft.server.v1_4_R1.Block.byId.length + && net.minecraft.server.v1_4_R1.Block.byId[type] != null); + } +} diff --git a/src/main/java/com/sk89q/worldedit/bukkit/NmsBlock.java b/src/main/java/com/sk89q/worldedit/bukkit/NmsBlock.java index b95543182..46bd635b0 100644 --- a/src/main/java/com/sk89q/worldedit/bukkit/NmsBlock.java +++ b/src/main/java/com/sk89q/worldedit/bukkit/NmsBlock.java @@ -1,442 +1,42 @@ -// $Id$ -/* - * This file is a part of WorldEdit. - * Copyright (c) sk89q - * Copyright (c) the 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 - * (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 - * GNU General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License along with - * this program. If not, see . - */ - -package com.sk89q.worldedit.bukkit; - -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.logging.Logger; - -import net.minecraft.server.v1_4_6.NBTBase; -import net.minecraft.server.v1_4_6.NBTTagByte; -import net.minecraft.server.v1_4_6.NBTTagByteArray; -import net.minecraft.server.v1_4_6.NBTTagCompound; -import net.minecraft.server.v1_4_6.NBTTagDouble; -import net.minecraft.server.v1_4_6.NBTTagEnd; -import net.minecraft.server.v1_4_6.NBTTagFloat; -import net.minecraft.server.v1_4_6.NBTTagInt; -import net.minecraft.server.v1_4_6.NBTTagIntArray; -import net.minecraft.server.v1_4_6.NBTTagList; -import net.minecraft.server.v1_4_6.NBTTagLong; -import net.minecraft.server.v1_4_6.NBTTagShort; -import net.minecraft.server.v1_4_6.NBTTagString; -import net.minecraft.server.v1_4_6.TileEntity; - -import org.bukkit.World; -import org.bukkit.craftbukkit.v1_4_6.CraftWorld; - -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.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.Vector; -import com.sk89q.worldedit.blocks.BaseBlock; -import com.sk89q.worldedit.blocks.TileEntityBlock; -import com.sk89q.worldedit.data.DataException; -import com.sk89q.worldedit.foundation.Block; - -/** - * A blind handler of blocks with TileEntity data that directly access Minecraft's - * classes through CraftBukkit. - *

- * Usage of this class may break terribly in the future, and therefore usage should - * be trapped in a handler for {@link Throwable}. - */ -class NmsBlock extends BaseBlock implements TileEntityBlock { - - private static final Logger logger = Logger.getLogger(NmsBlock.class.getCanonicalName()); - private static Field compoundMapField; - private static final Field nmsBlock_isTileEntityField; // The field is deobfuscated but the method isn't. No idea why. - private NBTTagCompound nbtData = null; - - static { - Field field; - try { - field = net.minecraft.server.v1_4_6.Block.class.getDeclaredField("isTileEntity"); - field.setAccessible(true); - } catch (NoSuchFieldException e) { - logger.severe("Could not find NMS block tile entity field!"); - field = null; - } - nmsBlock_isTileEntityField = field; - } - - /** - * Create a new instance with a given type ID, data value, and previous - * {@link TileEntityBlock}-implementing object. - * - * @param type block type ID - * @param data data value - * @param tileEntityBlock tile entity block - */ - public NmsBlock(int type, int data, TileEntityBlock tileEntityBlock) { - super(type, data); - - nbtData = (NBTTagCompound) fromNative(tileEntityBlock.getNbtData()); - } - - /** - * Create a new instance with a given type ID, data value, and raw - * {@link NBTTagCompound} copy. - * - * @param type block type ID - * @param data data value - * @param nbtData raw NBT data - */ - public NmsBlock(int type, int data, NBTTagCompound nbtData) { - super(type, data); - - this.nbtData = nbtData; - } - - /** - * Build a {@link NBTTagCompound} that has valid coordinates. - * - * @param pt coordinates to set - * @return the tag compound - */ - private NBTTagCompound getNmsData(Vector pt) { - if (nbtData == null) { - return null; - } - - nbtData.set("x", new NBTTagInt("x", pt.getBlockX())); - nbtData.set("y", new NBTTagInt("y", pt.getBlockY())); - nbtData.set("z", new NBTTagInt("z", pt.getBlockZ())); - - return nbtData; - } - - @Override - public boolean hasNbtData() { - return nbtData != null; - } - - @Override - public String getNbtId() { - if (nbtData == null) { - return ""; - } - - return nbtData.getString("id"); - } - - @Override - public CompoundTag getNbtData() { - if (nbtData == null) { - return new CompoundTag(getNbtId(), - new HashMap()); - } - return (CompoundTag) toNative(nbtData); - } - - @Override - public void setNbtData(CompoundTag tag) throws DataException { - if (tag == null) { - this.nbtData = null; - } - this.nbtData = (NBTTagCompound) fromNative(tag); - } - - /** - * Build an instance from the given information. - * - * @param world world to get the block from - * @param position position to get the block at - * @param type type ID of block - * @param data data value of block - * @return the block, or null - */ - public static NmsBlock get(World world, Vector position, int type, int data) { - if (!hasTileEntity(type)) { - return null; - } - - TileEntity te = ((CraftWorld) world).getHandle().getTileEntity( - position.getBlockX(), position.getBlockY(), position.getBlockZ()); - - if (te != null) { - NBTTagCompound tag = new NBTTagCompound(); - te.b(tag); // Load data - return new NmsBlock(type, data, tag); - } - - return null; - } - - /** - * Set an instance or a {@link TileEntityBlock} to the given position. - * - * @param world world to set the block in - * @param position position to set the block at - * @param block the block to set - * @return true if tile entity data was copied to the world - */ - public static boolean set(World world, Vector position, BaseBlock block) { - NBTTagCompound data = null; - if (!hasTileEntity(world.getBlockTypeIdAt(position.getBlockX(), position.getBlockY(), position.getBlockZ()))) { - return false; - } - - if (block instanceof NmsBlock) { - NmsBlock nmsProxyBlock = (NmsBlock) block; - data = nmsProxyBlock.getNmsData(position); - } else if (block instanceof TileEntityBlock) { - NmsBlock nmsProxyBlock = new NmsBlock( - block.getType(), block.getData(), block); - data = nmsProxyBlock.getNmsData(position); - } - - if (data != null) { - TileEntity te = ((CraftWorld) world).getHandle().getTileEntity( - position.getBlockX(), position.getBlockY(), position.getBlockZ()); - if (te != null) { - te.a(data); // Load data - return true; - } - } - - return false; - } - - /** - * Tries to set a block 'safely', as in setting the block data to the location, and - * then triggering physics only at the end. - * - * @param world world to set the block in - * @param position position to set the block at - * @param block the block to set - * @param notifyAdjacent true to notify physics and what not - * @return true if block id or data was changed - */ - public static boolean setSafely(BukkitWorld world, Vector position, - Block block, boolean notifyAdjacent) { - - int x = position.getBlockX(); - int y = position.getBlockY(); - int z = position.getBlockZ(); - - CraftWorld craftWorld = ((CraftWorld) world.getWorld()); - - boolean changed = craftWorld.getHandle().setRawTypeIdAndData( - x, y, z, block.getId(), block.getData()); - - if (block instanceof BaseBlock) { - world.copyToWorld(position, (BaseBlock) block); - } - - if (changed) { - if (notifyAdjacent) { - craftWorld.getHandle().update(x, y, z, block.getId()); - } else { - craftWorld.getHandle().notify(x, y, z); - } - } - - return changed; - } - - public static boolean hasTileEntity(int type) { - net.minecraft.server.v1_4_6.Block nmsBlock = getNmsBlock(type); - if (nmsBlock == null) { - return false; - } - - try { - return nmsBlock_isTileEntityField.getBoolean(nmsBlock); // Once we have the field stord, gets are fast - } catch (IllegalAccessException e) { - return false; - } - } - - public static net.minecraft.server.v1_4_6.Block getNmsBlock(int type) { - if (type < 0 || type >= net.minecraft.server.v1_4_6.Block.byId.length) { - return null; - } - return net.minecraft.server.v1_4_6.Block.byId[type]; - } - - /** - * 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") - private static Tag toNative(NBTBase foreign) { - if (foreign == null) { - return null; - } - if (foreign instanceof NBTTagCompound) { - Map values = new HashMap(); - Collection foreignValues = null; - - if (compoundMapField == null) { - try { - // Method name may change! - foreignValues = ((NBTTagCompound) foreign).c(); - } catch (Throwable t) { - try { - logger.warning("WorldEdit: Couldn't get NBTTagCompound.c(), " + - "so we're going to try to get at the 'map' field directly from now on"); - - if (compoundMapField == null) { - compoundMapField = NBTTagCompound.class.getDeclaredField("map"); - compoundMapField.setAccessible(true); - } - } catch (Throwable e) { - // Can't do much beyond this - throw new RuntimeException(e); - } - } - } - - if (compoundMapField != null) { - try { - foreignValues = ((HashMap) compoundMapField.get(foreign)).values(); - } catch (Throwable e) { - // Can't do much beyond this - throw new RuntimeException(e); - } - } - - for (Object obj : foreignValues) { - NBTBase base = (NBTBase) obj; - values.put(base.getName(), toNative(base)); - } - return new CompoundTag(foreign.getName(), values); - } else if (foreign instanceof NBTTagByte) { - return new ByteTag(foreign.getName(), ((NBTTagByte) foreign).data); - } else if (foreign instanceof NBTTagByteArray) { - return new ByteArrayTag(foreign.getName(), - ((NBTTagByteArray) foreign).data); - } else if (foreign instanceof NBTTagDouble) { - return new DoubleTag(foreign.getName(), - ((NBTTagDouble) foreign).data); - } else if (foreign instanceof NBTTagFloat) { - return new FloatTag(foreign.getName(), ((NBTTagFloat) foreign).data); - } else if (foreign instanceof NBTTagInt) { - return new IntTag(foreign.getName(), ((NBTTagInt) foreign).data); - } else if (foreign instanceof NBTTagIntArray) { - return new IntArrayTag(foreign.getName(), - ((NBTTagIntArray) foreign).data); - } else if (foreign instanceof NBTTagList) { - List values = new ArrayList(); - NBTTagList foreignList = (NBTTagList) foreign; - int type = NBTConstants.TYPE_BYTE; - for (int i = 0; i < foreignList.size(); i++) { - NBTBase foreignTag = foreignList.get(i); - values.add(toNative(foreignTag)); - type = foreignTag.getTypeId(); - } - Class cls = NBTConstants.getClassFromType(type); - return new ListTag(foreign.getName(), cls, values); - } else if (foreign instanceof NBTTagLong) { - return new LongTag(foreign.getName(), ((NBTTagLong) foreign).data); - } else if (foreign instanceof NBTTagShort) { - return new ShortTag(foreign.getName(), ((NBTTagShort) foreign).data); - } else if (foreign instanceof NBTTagString) { - return new StringTag(foreign.getName(), - ((NBTTagString) foreign).data); - } else if (foreign instanceof NBTTagEnd) { - return new EndTag(); - } else { - throw new IllegalArgumentException("Don't know how to make native " - + foreign.getClass().getCanonicalName()); - } - } - - /** - * Converts a WorldEdit-native NBT structure to a NMS structure. - * - * @param foreign structure to convert - * @return non-native structure - */ - private static NBTBase fromNative(Tag foreign) { - if (foreign == null) { - return null; - } - if (foreign instanceof CompoundTag) { - NBTTagCompound tag = new NBTTagCompound(foreign.getName()); - 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(foreign.getName(), - ((ByteTag) foreign).getValue()); - } else if (foreign instanceof ByteArrayTag) { - return new NBTTagByteArray(foreign.getName(), - ((ByteArrayTag) foreign).getValue()); - } else if (foreign instanceof DoubleTag) { - return new NBTTagDouble(foreign.getName(), - ((DoubleTag) foreign).getValue()); - } else if (foreign instanceof FloatTag) { - return new NBTTagFloat(foreign.getName(), - ((FloatTag) foreign).getValue()); - } else if (foreign instanceof IntTag) { - return new NBTTagInt(foreign.getName(), - ((IntTag) foreign).getValue()); - } else if (foreign instanceof IntArrayTag) { - return new NBTTagIntArray(foreign.getName(), - ((IntArrayTag) foreign).getValue()); - } else if (foreign instanceof ListTag) { - NBTTagList tag = new NBTTagList(foreign.getName()); - ListTag foreignList = (ListTag) foreign; - for (Tag t : foreignList.getValue()) { - tag.add(fromNative(t)); - } - return tag; - } else if (foreign instanceof LongTag) { - return new NBTTagLong(foreign.getName(), - ((LongTag) foreign).getValue()); - } else if (foreign instanceof ShortTag) { - return new NBTTagShort(foreign.getName(), - ((ShortTag) foreign).getValue()); - } else if (foreign instanceof StringTag) { - return new NBTTagString(foreign.getName(), - ((StringTag) foreign).getValue()); - } else if (foreign instanceof EndTag) { - return new NBTTagEnd(); - } else { - throw new IllegalArgumentException("Don't know how to make NMS " - + foreign.getClass().getCanonicalName()); - } - } - - public static boolean isValidBlockType(int type) throws NoClassDefFoundError { - return type == 0 || (type >= 1 && type < net.minecraft.server.v1_4_6.Block.byId.length - && net.minecraft.server.v1_4_6.Block.byId[type] != null); - } -} +package com.sk89q.worldedit.bukkit; + +import org.bukkit.World; + +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.foundation.Block; + +public abstract class NmsBlock extends BaseBlock { + + protected NmsBlock(int type) { + super(type); + } + + protected NmsBlock(int type, int data) { + super(type, data); + } + + public static boolean verify() { + return false; + } + + public static NmsBlock get(World world, Vector vector, int type, int data) { + return null; + } + + public static boolean set(World world, Vector vector, Block block) { + return false; + } + + public static boolean setSafely(World world, Vector vector, Block block, boolean notify) { + return false; + } + + public static boolean hasTileEntity(int type) { + return false; + } + + public static boolean isValidBlockType(int type) { + return false; + } +} \ No newline at end of file diff --git a/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java b/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java index 1d9cdafea..b7b9826e1 100644 --- a/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java +++ b/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java @@ -28,6 +28,7 @@ import java.util.jar.JarFile; import java.util.logging.Handler; import java.util.zip.ZipEntry; +import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -95,6 +96,7 @@ public class WorldEditPlugin extends JavaPlugin { // Make the data folders that WorldEdit uses getDataFolder().mkdirs(); + new File(getDataFolder() + File.separator + "nmsblocks").mkdir(); // Create the default configuration file createDefaultConfiguration("config.yml"); @@ -110,15 +112,16 @@ public class WorldEditPlugin extends JavaPlugin { // Setup interfaces server = new BukkitServerInterface(this, getServer()); controller = new WorldEdit(server, config); + WorldEdit.getInstance().logger.setParent(Bukkit.getLogger()); api = new WorldEditAPI(this); getServer().getMessenger().registerIncomingPluginChannel(this, CUI_PLUGIN_CHANNEL, new CUIChannelListener(this)); getServer().getMessenger().registerOutgoingPluginChannel(this, CUI_PLUGIN_CHANNEL); - // Now we can register events! getServer().getPluginManager().registerEvents(new WorldEditListener(this), this); getServer().getScheduler().scheduleAsyncRepeatingTask(this, new SessionTimer(controller, getServer()), 120, 120); + } /**