/* * This file is part of SpoutcraftPlugin. * * Copyright (c) 2011 SpoutcraftDev <http://spoutcraft.org//> * SpoutcraftPlugin is licensed under the GNU Lesser General Public License. * * SpoutcraftPlugin is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * SpoutcraftPlugin is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.getspout.spout.block.mcblock; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Arrays; import java.util.concurrent.ConcurrentHashMap; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import net.minecraft.server.v1_6_R3.Block; import net.minecraft.server.v1_6_R3.BlockButtonAbstract; import net.minecraft.server.v1_6_R3.BlockDiodeAbstract; import net.minecraft.server.v1_6_R3.BlockFurnace; import net.minecraft.server.v1_6_R3.BlockHalfTransparant; import net.minecraft.server.v1_6_R3.BlockMinecartTrack; import net.minecraft.server.v1_6_R3.BlockPressurePlateAbstract; import net.minecraft.server.v1_6_R3.BlockPressurePlateBinary; import net.minecraft.server.v1_6_R3.BlockPumpkin; import net.minecraft.server.v1_6_R3.BlockRedstoneOre; import net.minecraft.server.v1_6_R3.BlockRedstoneTorch; import net.minecraft.server.v1_6_R3.BlockStem; import net.minecraft.server.v1_6_R3.BlockStepAbstract; import net.minecraft.server.v1_6_R3.Entity; import net.minecraft.server.v1_6_R3.EntityHuman; import net.minecraft.server.v1_6_R3.EntityPlayer; import net.minecraft.server.v1_6_R3.EnumMobType; import net.minecraft.server.v1_6_R3.IBlockAccess; import net.minecraft.server.v1_6_R3.Material; import net.minecraft.server.v1_6_R3.StepSound; import net.minecraft.server.v1_6_R3.World; import org.getspout.spout.block.SpoutCraftBlock; import org.getspout.spoutapi.SpoutManager; import org.getspout.spoutapi.block.SpoutBlock; import org.getspout.spoutapi.inventory.SpoutItemStack; import org.getspout.spoutapi.material.CustomItem; import org.getspout.spoutapi.material.MaterialData; import org.getspout.spoutapi.material.Tool; import org.getspout.spoutapi.player.SpoutPlayer; public final class CustomMCBlock implements MethodInterceptor { private final Block wrapped; private CustomMCBlock(Block toWrap) { this.wrapped = toWrap; } public Block getWrapped() { return wrapped; } private org.getspout.spoutapi.material.CustomBlock getCustomBlock(World world, int x, int y, int z) { short[] customIds = SpoutManager.getChunkDataManager().getCustomBlockIds(world.getWorld(), x >> 4, z >> 4); if (customIds != null) { int index = getIndex(x, y, z); short id = customIds[index]; return MaterialData.getCustomBlock(id); } return null; } protected static int getIndex(int x, int y, int z) { return (x & 0xF) << 0xC | (z & 0xF) << 0x8 | (y & 0xFF); } @SuppressWarnings("rawtypes") private static Block createProxy(Block parent) { Enhancer enc = new Enhancer(); enc.setSuperclass(parent.getClass()); enc.setInterfaces(new Class[] { WrappedMCBlock.class }); enc.setCallback(new CustomMCBlock(parent)); enc.setClassLoader(CustomMCBlock.class.getClassLoader()); Constructor<?>[] constructors = parent.getClass().getDeclaredConstructors(); BlockConstructor use = null; for (Constructor<?> c : constructors) { c.setAccessible(true); for (BlockConstructor type : BlockConstructor.values()) { if (Arrays.deepEquals(c.getParameterTypes(), type.constructor)) { use = type; break; } } } if (use == null) { System.err.println("Unable to find matching constructor for class: " + parent.getClass().getName()); for (Constructor<?> c : constructors) { System.err.println(" " + Arrays.toString(c.getParameterTypes())); } return null; } try { Block proxy; switch(use) { case None: proxy = (Block) enc.create(); break; case Id: proxy = (Block) enc.create(use.constructor, new Object[] {parent.id}); break; case IdAndStep: { boolean field2; if (parent instanceof BlockStepAbstract) { field2 = ((Boolean)getField(BlockStepAbstract.class, parent, "a")).booleanValue(); } else if (parent instanceof BlockFurnace) { field2 = ((Boolean)getField(parent, "b")).booleanValue(); } else if (parent instanceof BlockRedstoneTorch) { field2 = ((Boolean)getField(parent, "isOn")).booleanValue(); } else if (parent instanceof BlockDiodeAbstract) { field2 = ((Boolean)getField(BlockDiodeAbstract.class, parent, "a")).booleanValue(); } else { field2 = ((Boolean)getField(parent, "a")).booleanValue(); } proxy = (Block) enc.create(use.constructor, new Object[] {parent.id, field2}); } break; case IdAndMaterial: proxy = (Block) enc.create(use.constructor, new Object[] {parent.id, parent.material}); break; case IdAndTexture: proxy = (Block) enc.create(use.constructor, new Object[] {parent.id, (Integer)getField(parent, "a")}); break; case IdTextureAndMaterial: proxy = (Block) enc.create(use.constructor, new Object[] {parent.id, parent.material}); break; case IdMaterialAndFlag: proxy = (Block) enc.create(use.constructor, new Object[] {parent.id, parent.material, false}); break; case IdAndName: { String name = (String) getField(parent, "a"); proxy = (Block) enc.create(use.constructor, new Object[] {parent.id, name}); } break; case IdNameAndMaterial: { String name = (String) getField(parent, "a"); proxy = (Block) enc.create(use.constructor, new Object[] {parent.id, name, parent.material}); } break; case IdNameMaterialAndDrop: { String name = (String) getField(BlockPressurePlateAbstract.class, parent, "a"); int field4 = (Integer) getField(parent, "a"); proxy = (Block) enc.create(use.constructor, new Object[] {parent.id, name, parent.material, field4}); } break; case IdBlockAndOther: { Block field2 = ((Block)getField(parent, "b")); int field3 = ((Integer)getField(parent, "c")); proxy = (Block) enc.create(use.constructor, new Object[] {parent.id, field2, field3}); } break; case IdMaterialAndDrop: { int field3 = ((Integer)getField(parent, "b")); proxy = (Block) enc.create(use.constructor, new Object[] {parent.id, parent.material, field3}); } break; case SignBlock: { Class field2 = ((Class)getField(parent, "a")); boolean field3 = ((Boolean)getField(parent, "b")); proxy = (Block) enc.create(use.constructor, new Object[] {parent.id, field2, field3}); } break; case IdTextureAndTicks: { boolean field3; if (parent instanceof BlockMinecartTrack || parent instanceof BlockRedstoneOre || parent instanceof BlockButtonAbstract || parent instanceof BlockPumpkin) { field3 = ((Boolean)getField(parent, "a")).booleanValue(); } else { field3 = ((Boolean)getField(parent, "isTicking")).booleanValue(); } proxy = (Block) enc.create(use.constructor, new Object[] {parent.id, field3}); } break; case PressurePlate: { EnumMobType field3 = ((EnumMobType)getField(parent, "a")); proxy = (Block) enc.create(use.constructor, new Object[] {parent.id, field3, parent.material}); } break; case HugeMushroom: { int field4 = ((Integer)getField(parent, "a")); proxy = (Block) enc.create(use.constructor, new Object[] {parent.id, parent.material, field4}); } break; case BlockStem: { Block field2; if (parent instanceof BlockStem) { field2 = ((Block)getField(parent, "blockFruit")); } else { field2 = Block.COBBLESTONE; } proxy = (Block) enc.create(use.constructor, new Object[] {parent.id, field2}); } break; case IdTextureMaterialAndTransparent: { boolean field4 = ((Boolean)getField(BlockHalfTransparant.class, parent, "a")).booleanValue(); proxy = (Block) enc.create(use.constructor, new Object[] {parent.id, parent.material, field4}); } break; case IdTextureDataMaterialAndDrops: { int field3 = (Integer) getField(parent, "a"); boolean field5 = ((Boolean)getField(parent, "b")).booleanValue(); proxy = (Block) enc.create(use.constructor, new Object[] {parent.id, field3, parent.material, field5}); } break; case IdNameMaterialAndMobType: { String name = (String) getField(BlockPressurePlateAbstract.class, parent, "a"); EnumMobType mobs = (EnumMobType) getField(BlockPressurePlateBinary.class, parent, "a"); proxy = (Block) enc.create(use.constructor, new Object[] {parent.id, name, parent.material, mobs}); } break; case IdStringStringMaterialAndFlag: { String field2 = (String) getField(parent, "a"); String field3 = (String) getField(parent, "c"); boolean field5 = (Boolean) getField(parent, "b"); proxy = (Block) enc.create(use.constructor, new Object[] {parent.id, field2, field3, parent.material, field5}); } break; default: throw new IllegalStateException("Unknown type " + use); } // Restore Parent StepSound if (proxy !=null) { proxy.stepSound = parent.stepSound; } return proxy; } catch (RuntimeException e) { System.err.println("Error creating : " + parent.getClass().getName() + " with constructor: " + use.name()); e.printStackTrace(); return null; } } public static void replaceBlocks() { //Store proper lighting values for opacity final int[] lightOpacity = new int[Block.lightBlock.length]; System.arraycopy(Block.lightBlock, 0, lightOpacity, 0, lightOpacity.length); //Store...whatever t is final boolean[] sArray = new boolean[Block.t.length]; System.arraycopy(Block.t, 0, sArray, 0, Block.t.length); //Store...whatever v is final boolean[] uArray = new boolean[Block.v.length]; System.arraycopy(Block.v, 0, sArray, 0, Block.v.length); //Store...whatever x is final boolean[] wArray = new boolean[Block.x.length]; System.arraycopy(Block.x, 0, wArray, 0, Block.x.length); //Store proper lighting values for emission final int[] lightEmission = new int[Block.lightEmission.length]; System.arraycopy(Block.lightEmission, 0, lightEmission, 0, Block.lightEmission.length); for (int i = 0; i < Block.byId.length; i++) { if (Block.byId[i] != null) { Block parent = Block.byId[i]; Block.byId[i] = null; float strength = 0; try { final Field field = getField(parent.getClass(), "strength"); field.setAccessible(true); strength = field.getFloat(parent); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } float durability = 0; try { final Field field = getField(parent.getClass(), "durability"); field.setAccessible(true); durability = field.getFloat(parent); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } try { Block fake = createProxy(parent); if (fake != null) { Block.byId[i] = fake; } else { Block.byId[i] = parent; } final Field strengthField = getField(fake.getClass(), "strength"); strengthField.setAccessible(true); strengthField.setFloat(fake, strength); final Field durabilityField = getField(fake.getClass(), "durability"); durabilityField.setAccessible(true); durabilityField.setFloat(fake, durability); } catch (Throwable t) { System.err.println("Error replacing : " + parent.getClass().getName()); t.printStackTrace(); Block.byId[i] = parent; } } } //Fix values System.arraycopy(lightOpacity, 0, Block.lightBlock, 0, lightOpacity.length); //System.arraycopy(sArray, 0, Block.t, 0, sArray.length); //System.arraycopy(uArray, 0, Block.v, 0, uArray.length); //System.arraycopy(wArray, 0, Block.x, 0, wArray.length); System.arraycopy(lightEmission, 0, Block.lightEmission, 0, lightEmission.length); } @SuppressWarnings("rawtypes") private static enum BlockConstructor { None(new Class[0]), Id(new Class[] {int.class}), IdAndStep(new Class[] {int.class, boolean.class}), IdAndMaterial(new Class[] {int.class, Material.class}), IdAndTexture(new Class[] {int.class, int.class}), IdAndName(new Class[] {int.class, String.class}), IdMaterialAndDrop(new Class[] {int.class, Material.class, int.class}), IdNameAndMaterial(new Class[] {int.class, String.class, Material.class}), IdNameMaterialAndDrop(new Class[] {int.class, String.class, Material.class, int.class}), IdMaterialAndFlag(new Class[] {int.class, Material.class, boolean.class}), IdBlockAndOther(new Class[] {int.class, Block.class, int.class}), SignBlock(new Class[] {int.class, Class.class, boolean.class}), PressurePlate(new Class[] {int.class, int.class, EnumMobType.class, Material.class}), HugeMushroom(new Class[] {int.class, Material.class, int.class, int.class}), BlockStem(new Class[] {int.class, Block.class}), IdTextureAndTicks(new Class[] {int.class, int.class, boolean.class}), IdTextureAndMaterial(new Class[] {int.class, int.class, Material.class}), IdTextureMaterialAndTransparent(new Class[] {int.class, int.class, Material.class, boolean.class}), IdTextureDataMaterialAndDrops(new Class[] {int.class, int.class, int.class, Material.class, boolean.class}), IdNameMaterialAndMobType(new Class[]{int.class, String.class, Material.class, EnumMobType.class}), IdStringStringMaterialAndFlag(new Class[] {int.class, String.class, String.class, Material.class, boolean.class}); private final Class[] constructor; BlockConstructor(Class[] constructor) { this.constructor = constructor; } ; } // TODO This causes redstone issues with glass (allows power through glass, vanilla does not) but it can't // Be solved from a plugin. Implemented workaround: Don't update glass unless non-opaque custom blocks are on the server. public static void updateGlass() { // Allow placement of blocks on glass try { Field field = Material.SHATTERABLE.getClass().getDeclaredField("J"); field.setAccessible(true); field.setBoolean(Material.SHATTERABLE, false); } catch (Exception e) { e.printStackTrace(); } Block.lightBlock[Block.GLASS.id] = 0; } public static void resetBlocks() { for (int i = 0; i < Block.byId.length; i++) { if (Block.byId[i] != null) { Block parent = Block.byId[i]; if (parent instanceof WrappedMCBlock) { WrappedMCBlock wrapped = (WrappedMCBlock) parent; Block.byId[i] = null; Block.byId[i] = wrapped.getWrapped(); } } } } private static Object getField(Block parent, String fieldName) { return getField(parent.getClass(), parent, fieldName); } private static Object getField(Class<?> clazz, Block parent, String fieldName) { try { Field field = clazz.getDeclaredField(fieldName); field.setAccessible(true); return field.get(parent); } catch (Exception e) { throw new RuntimeException(e); } } @SuppressWarnings("rawtypes") private static Method findRecursive(Class<?> clazz, String name, Class[] args) { for (Method m : clazz.getDeclaredMethods()) { if (m.getName().equals(name)) { if (Arrays.deepEquals(m.getParameterTypes(), args)) { return m; } } } if (clazz.getSuperclass() != null) { return findRecursive(clazz.getSuperclass(), name, args); } return null; } private final ConcurrentHashMap<Method, Method> fastMethodCache = new ConcurrentHashMap<Method, Method>(100); @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { Method m = fastMethodCache.get(method); if (m == null) { // Special cases if (method.getName().equals("getWrapped")) { return getWrapped(); } else if (method.getName().equals("setHardness")) { Field f = Block.class.getDeclaredField("strength"); f.setAccessible(true); f.set(wrapped, args[0]); return null; } else { m = findRecursive(wrapped.getClass(), method.getName(), method.getParameterTypes()); if (m != null) { m.setAccessible(true); fastMethodCache.put(method, m); } else { System.err.println("Unable to find method : " + method.getName() + " with: " + Arrays.toString(method.getParameterTypes())); return null; } } } // Overridden methods if (method.getName().equals("a") && Arrays.deepEquals(method.getParameterTypes(), new Class[] {IBlockAccess.class, int.class, int.class, int.class, int.class})) { World world = (World) args[0]; int x = (Integer) args[1]; int y = (Integer) args[2]; int z = (Integer) args[3]; int face = (Integer) args[4]; org.getspout.spoutapi.material.CustomBlock block = getCustomBlock(world, x, y, z); if (block != null) { return block.isProvidingPowerTo(world.getWorld(), x, y, z, org.bukkit.craftbukkit.v1_6_R3.block.CraftBlock.notchToBlockFace(face)); } } else if (method.getName().equals("c") && Arrays.deepEquals(method.getParameterTypes(), new Class[] {World.class, int.class, int.class, int.class, int.class})) { World world = (World) args[0]; int x = (Integer) args[1]; int y = (Integer) args[2]; int z = (Integer) args[3]; int face = (Integer) args[4]; org.getspout.spoutapi.material.CustomBlock block = getCustomBlock(world, x, y, z); if (block != null) { return block.isProvidingPowerTo(world.getWorld(), x, y, z, org.bukkit.craftbukkit.v1_6_R3.block.CraftBlock.notchToBlockFace(face)); } } else if (method.getName().equals("b") && Arrays.deepEquals(method.getParameterTypes(), new Class[] {World.class, int.class, int.class, int.class, Entity.class})) { World world = (World) args[0]; int x = (Integer) args[1]; int y = (Integer) args[2]; int z = (Integer) args[3]; Entity e = (Entity) args[4]; org.getspout.spoutapi.material.CustomBlock block = getCustomBlock(world, x, y, z); if (block != null) { block.onEntityMoveAt(world.getWorld(), x, y, z, e.getBukkitEntity()); return null; } } else if (method.getName().equals("interact")) { World world = (World) args[0]; int x = (Integer) args[1]; int y = (Integer) args[2]; int z = (Integer) args[3]; Entity human = (Entity) args[4]; org.getspout.spoutapi.material.CustomBlock block = getCustomBlock(world, x, y, z); if (block != null && human instanceof EntityPlayer) { return block.onBlockInteract(world.getWorld(), x, y, z, ((SpoutPlayer) human.getBukkitEntity())); } } else if (method.getName().equals("getDamage")) { EntityHuman human = (EntityHuman) args[0]; World world = (World) args[1]; int x = (Integer) args[2]; int y = (Integer) args[3]; int z = (Integer) args[4]; org.getspout.spoutapi.material.CustomBlock block = getCustomBlock(world, x, y, z); if (block != null) { if (block instanceof org.getspout.spoutapi.material.CustomBlock) { SpoutPlayer player = (SpoutPlayer) human.getBukkitEntity(); float def; SpoutItemStack inHand = player.getItemInHand() == null ? null : new SpoutItemStack(player.getItemInHand()); org.getspout.spoutapi.material.Material item = inHand.getMaterial(); float hardness = block.getHardness(); if (hardness <= 0F) { return m.invoke(wrapped, args); } def = (!human.a(wrapped) ? 1.0F / hardness / 100.0F : human.a(wrapped, false) / hardness / 30.0F); //TODO EntityHuman.a(Block, boolean) appears to not make any use of the flag variable... if (!(item instanceof CustomItem)) { return def; } if (!(item instanceof Tool)) { return def; } Tool tool = (Tool) item; float modifier = tool.getStrengthModifier(block); return modifier / hardness / (modifier > 1F ? 30F : 100F); } } } else if (method.getName().equals("remove")) { World world = (World) args[0]; int x = (Integer) args[1]; int y = (Integer) args[2]; int z = (Integer) args[3]; org.getspout.spoutapi.material.CustomBlock block = getCustomBlock(world, x, y, z); if (block != null) { block.onBlockDestroyed(world.getWorld(), x, y, z); } } else if (method.getName().equals("doPhysics")) { World world = (World) args[0]; int x = (Integer) args[1]; int y = (Integer) args[2]; int z = (Integer) args[3]; int face = (Integer) args[4]; org.getspout.spoutapi.material.CustomBlock block = getCustomBlock(world, x, y, z); if (block != null) { block.onNeighborBlockChange(world.getWorld(), x, y, z, face); } } return m.invoke(wrapped, args); } private static Field getField(Class clazz, String fieldName) throws NoSuchFieldException { try { return clazz.getDeclaredField(fieldName); } catch (NoSuchFieldException e) { Class superClass = clazz.getSuperclass(); if (superClass == null) { throw e; } else { return getField(superClass, fieldName); } } } }