/*
* Copyright (C) 2013-2016 Gonçalo Baltazar <me@goncalomb.com>
*
* This file is part of NBTEditor.
*
* NBTEditor is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* NBTEditor 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with NBTEditor. If not, see <http://www.gnu.org/licenses/>.
*/
package com.goncalomb.bukkit.mylib.reflect;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.entity.Entity;
import org.bukkit.inventory.ItemStack;
public final class NBTUtils {
// Minecraft's ItemStack Class;
private static Method _ItemStack_createStack;
private static Method _ItemStack_save;
private static Method _ItemStack_getTag;
private static Method _ItemStack_setTag;
// CraftItemStack Class;
private static Method _CraftItemStack_asCraftMirror;
private static Method _CraftItemStack_asCraftCopy;
private static Field _CraftItemStack_handle;
// Minecraft's Entity Class;
private static Method _Entity_e; // Save data to NBTTagCompound.
private static Method _Entity_getBukkitEntity;
// CraftEntity Class
private static Method _CraftEntity_getHandle;
// Minecraft's TileEntity
private static Method _TileEntity_a; // Load data from NBTTagCompound.
private static Method _TileEntity_save; // Save data to NBTTagCompound.
// CraftWorld
private static Method _CraftWorld_getHandle;
private static Method _CraftWorld_getTileEntityAt;
// Minecraft's ChunkRegionLoader Class
private static Method _ChunkRegionLoader_a; // Spawn an entity from a NBTCompound.
static void prepareReflection() throws SecurityException, NoSuchMethodException, NoSuchFieldException {
Class<?> nbtTagCompoundClass = BukkitReflect.getMinecraftClass("NBTTagCompound");
Class<?> minecraftItemStackClass = BukkitReflect.getMinecraftClass("ItemStack");
_ItemStack_createStack = minecraftItemStackClass.getMethod("createStack", nbtTagCompoundClass);
_ItemStack_save = minecraftItemStackClass.getMethod("save", nbtTagCompoundClass);
_ItemStack_getTag = minecraftItemStackClass.getMethod("getTag");
_ItemStack_setTag = minecraftItemStackClass.getMethod("setTag", nbtTagCompoundClass);
Class<?> craftItemStackClass = BukkitReflect.getCraftBukkitClass("inventory.CraftItemStack");
_CraftItemStack_asCraftMirror = craftItemStackClass.getMethod("asCraftMirror", minecraftItemStackClass);
_CraftItemStack_asCraftCopy = craftItemStackClass.getMethod("asCraftCopy", ItemStack.class);
_CraftItemStack_handle = craftItemStackClass.getDeclaredField("handle");
_CraftItemStack_handle.setAccessible(true);
Class<?> minecraftEntityClass = BukkitReflect.getMinecraftClass("Entity");
_Entity_e = minecraftEntityClass.getMethod("e", nbtTagCompoundClass);
_Entity_getBukkitEntity = minecraftEntityClass.getMethod("getBukkitEntity");
Class<?> craftEntityClass = BukkitReflect.getCraftBukkitClass("entity.CraftEntity");
_CraftEntity_getHandle = craftEntityClass.getMethod("getHandle");
Class<?> minecraftTileEntityClass = BukkitReflect.getMinecraftClass("TileEntity");
_TileEntity_save = minecraftTileEntityClass.getMethod("save", nbtTagCompoundClass);
_TileEntity_a = minecraftTileEntityClass.getMethod("a", nbtTagCompoundClass);
Class<?> craftWorldClass = BukkitReflect.getCraftBukkitClass("CraftWorld");
_CraftWorld_getHandle = craftWorldClass.getMethod("getHandle");
_CraftWorld_getTileEntityAt = craftWorldClass.getMethod("getTileEntityAt", int.class, int.class, int.class);
Class<?> minecraftWorldClass = BukkitReflect.getMinecraftClass("World");
Class<?> minecraftChunkRegionLoaderClass = BukkitReflect.getMinecraftClass("ChunkRegionLoader");
_ChunkRegionLoader_a = minecraftChunkRegionLoaderClass.getMethod("a", nbtTagCompoundClass, minecraftWorldClass, double.class, double.class, double.class, boolean.class);
// This is a fix to spawn TippedArrows. It registers the TippedArrow entity on the Minecraft code.
// It also fixes spawning TippedArrows with command blocks.
// 23 is the "Network Id" for TippedArrow (checked with the client).
// XXX: remove this when fixed by Mojang
try {
Class<?> minecraftEntityTypesClass = BukkitReflect.getMinecraftClass("EntityTypes");
Method _EntityTypes_a_AKA_registerEntity = minecraftEntityTypesClass.getDeclaredMethod("a", Class.class, String.class, int.class);
_EntityTypes_a_AKA_registerEntity.setAccessible(true);
_EntityTypes_a_AKA_registerEntity.invoke(null, BukkitReflect.getMinecraftClass("EntityTippedArrow"), "TippedArrow", 23);
} catch (Exception e) { }
}
private NBTUtils() { }
public static ItemStack itemStackFromNBTData(NBTTagCompound data) {
return (ItemStack) BukkitReflect.invokeMethod(null, _CraftItemStack_asCraftMirror, BukkitReflect.invokeMethod(null, _ItemStack_createStack, data._handle));
}
public static NBTTagCompound itemStackToNBTData(ItemStack stack) {
NBTTagCompound data = new NBTTagCompound();
Object handle = BukkitReflect.getFieldValue(stack, _CraftItemStack_handle);
BukkitReflect.invokeMethod(handle, _ItemStack_save, data._handle);
return data;
}
public static Entity spawnEntity(NBTTagCompound data, Location location) {
Object worldHandle = BukkitReflect.invokeMethod(location.getWorld(), _CraftWorld_getHandle);
Object entityHandle = BukkitReflect.invokeMethod(null, _ChunkRegionLoader_a, data._handle, worldHandle, location.getX(), location.getY(), location.getZ(), true);
if (entityHandle == null) {
return null;
}
return (Entity) BukkitReflect.invokeMethod(entityHandle, _Entity_getBukkitEntity);
}
public static NBTTagCompound getEntityNBTData(Entity entity) {
Object entityHandle = BukkitReflect.invokeMethod(entity, _CraftEntity_getHandle);
NBTTagCompound data = new NBTTagCompound();
BukkitReflect.invokeMethod(entityHandle, _Entity_e, data._handle);
return data;
}
public static NBTTagList potionToNBTEffectsList(ItemStack potion) {
NBTTagCompound tag = getItemStackTag(potion);
if (tag.hasKey("CustomPotionEffects")) {
return tag.getList("CustomPotionEffects").clone();
}
// Fallback to default potion effect.
// XXX: implement fallback
// Finding the default potion effect is not trivial on 1.9.
// Wait until org.bukkit.craftbukkit.potion.CraftPotionUtil is available upstream.
// For now, display some alert messages on InventoryForMobs and InventoryForThrownPotion.
return new NBTTagList();
}
public static ItemStack potionFromNBTEffectsList(NBTTagList effects) {
NBTTagCompound tag = new NBTTagCompound();
tag.setList("CustomPotionEffects", effects.clone());
tag.setString("Potion", "minecraft:empty");
NBTTagCompound data = new NBTTagCompound();
data.setString("id", "minecraft:potion");
data.setByte("Count", (byte) 1);
data.setShort("Damage", (short) 0);
data.setCompound("tag", tag);
return itemStackFromNBTData(data);
}
private static Object getTileEntity(Block block) {
return BukkitReflect.invokeMethod(block.getWorld(), _CraftWorld_getTileEntityAt, block.getX(), block.getY(), block.getZ());
}
public static NBTTagCompound getTileEntityNBTData(Block block) {
NBTBase.prepareReflection();
Object tileEntity = getTileEntity(block);
if (tileEntity != null) {
NBTTagCompound data = new NBTTagCompound();
BukkitReflect.invokeMethod(tileEntity, _TileEntity_save, data._handle);
return data;
}
return null;
}
public static void setTileEntityNBTData(Block block, NBTTagCompound data) {
NBTBase.prepareReflection();
Object tileEntity = getTileEntity(block);
if (tileEntity != null) {
BukkitReflect.invokeMethod(tileEntity, _TileEntity_a, data._handle);
}
}
public static NBTTagCompound getItemStackTag(ItemStack item) {
Object handle = BukkitReflect.getFieldValue(item, _CraftItemStack_handle);
Object tag = BukkitReflect.invokeMethod(handle, _ItemStack_getTag);
return (tag == null ? new NBTTagCompound() : new NBTTagCompound(tag));
}
public static void setItemStackTag(ItemStack item, NBTTagCompound tag) {
Object handle = BukkitReflect.getFieldValue(item, _CraftItemStack_handle);
BukkitReflect.invokeMethod(handle, _ItemStack_setTag, tag._handle);
}
public static ItemStack itemStackToCraftItemStack(ItemStack item) {
if (!_CraftItemStack_asCraftCopy.getClass().isInstance(item)) {
return (ItemStack) BukkitReflect.invokeMethod(null, _CraftItemStack_asCraftCopy, item);
}
return item;
}
}