From ad349aecb16b25ce56353fe7e9d7d6bc59914860 Mon Sep 17 00:00:00 2001 From: Wizjany Date: Thu, 17 Jan 2013 21:49:34 -0500 Subject: [PATCH] Support different NMS block classes depending on version. - The .class files in the contrib folder of the zip go in plugins/WorldEdit/nmsblocks - This allows us to swap new class files in without releasing a completely new version each time - Whatever version the last release is for has an inbuilt fallback - If the plugin and server are mismatched and you have nothing in nmsblocks you're screwed --- contrib/nmsblocks/CBXNmsBlock_146.class | Bin 0 -> 12404 bytes contrib/nmsblocks/CBXNmsBlock_146.java | 442 ++++++++++++++++ contrib/nmsblocks/MCPCPXNmsBlock.class | Bin 0 -> 11311 bytes contrib/nmsblocks/MCPCPXNmsBlock.java | 449 ++++++++++++++++ pom.xml | 2 +- src/main/assembly/default.xml | 1 + .../sk89q/worldedit/bukkit/BukkitWorld.java | 131 ++++- .../worldedit/bukkit/DefaultNmsBlock.java | 446 ++++++++++++++++ .../com/sk89q/worldedit/bukkit/NmsBlock.java | 484 ++---------------- .../worldedit/bukkit/WorldEditPlugin.java | 5 +- 10 files changed, 1509 insertions(+), 451 deletions(-) create mode 100644 contrib/nmsblocks/CBXNmsBlock_146.class create mode 100644 contrib/nmsblocks/CBXNmsBlock_146.java create mode 100644 contrib/nmsblocks/MCPCPXNmsBlock.class create mode 100644 contrib/nmsblocks/MCPCPXNmsBlock.java create mode 100644 src/main/java/com/sk89q/worldedit/bukkit/DefaultNmsBlock.java diff --git a/contrib/nmsblocks/CBXNmsBlock_146.class b/contrib/nmsblocks/CBXNmsBlock_146.class new file mode 100644 index 0000000000000000000000000000000000000000..0eca14c79a5300aee78ba3bb0869c7df1311ab50 GIT binary patch literal 12404 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 UVoc+CT!Oo7?XE?Bp6>DeA01ciWB>pF literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..95ba91ea0becfc0263c8c3fe06d092a017cb4eca GIT binary patch literal 11311 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 + * 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); + } /**