From 92bf3f702bbdf9ba66c81336562187504ccdb1d1 Mon Sep 17 00:00:00 2001 From: Jesse Boyd Date: Sun, 13 Aug 2017 12:38:19 +1000 Subject: [PATCH] Future proofing This is working towards making sure all parts of FAWE will work on newer versions of minecraft without the plugin needing to update. (it'll still be slower until proper support is added) - Adds version agnostic bukkit adapter - Adds version agnostic bukkit anvil mode --- .../com/boydti/fawe/bukkit/FaweBukkit.java | 2 + .../fawe/bukkit/v0/BukkitChunk_All.java | 8 +- .../boydti/fawe/bukkit/v0/BukkitQueue_0.java | 87 +++- .../fawe/bukkit/v0/BukkitQueue_All.java | 147 +++++- .../fawe/bukkit/v0/FaweAdapter_All.java | 451 ++++++++++++++++++ .../fawe/bukkit/v1_10/BukkitChunk_1_10.java | 6 +- .../fawe/bukkit/v1_10/BukkitQueue_1_10.java | 8 +- .../fawe/bukkit/v1_11/BukkitChunk_1_11.java | 6 +- .../fawe/bukkit/v1_11/BukkitQueue_1_11.java | 8 +- .../fawe/bukkit/v1_12/BukkitChunk_1_12.java | 8 +- .../fawe/bukkit/v1_12/BukkitQueue_1_12.java | 8 +- .../fawe/bukkit/v1_7/BukkitChunk_1_7.java | 4 +- .../fawe/bukkit/v1_7/BukkitQueue17.java | 6 +- .../fawe/bukkit/v1_8/BukkitChunk_1_8.java | 4 +- .../fawe/bukkit/v1_8/BukkitQueue18R3.java | 6 +- .../fawe/bukkit/v1_9/BukkitChunk_1_9.java | 4 +- .../fawe/bukkit/v1_9/BukkitQueue_1_9_R1.java | 10 +- .../fawe/bukkit/wrapper/AsyncWorld.java | 2 +- .../sk89q/worldedit/bukkit/BukkitWorld.java | 2 +- .../fawe/object/brush/CatenaryBrush.java | 30 +- .../com/boydti/fawe/util/ReflectionUtils.java | 66 +++ 21 files changed, 787 insertions(+), 86 deletions(-) create mode 100644 bukkit/src/main/java/com/boydti/fawe/bukkit/v0/FaweAdapter_All.java diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java index aaf73006..70d5d1a9 100644 --- a/bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java @@ -491,6 +491,8 @@ public class FaweBukkit implements IFawe, Listener { v1_10_R1, v1_11_R1, v1_12_R1, + v1_12_R2, + v1_13_R1, NONE, } diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitChunk_All.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitChunk_All.java index 569470e5..ee1ba91c 100644 --- a/bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitChunk_All.java +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitChunk_All.java @@ -208,7 +208,7 @@ public class BukkitChunk_All extends CharFaweChunk { int x = cacheX[m]; int z = cacheZ[m]; int id = combined >> 4; - if (FaweCache.hasNBT(id) && parent.adapter != null) { + if (FaweCache.hasNBT(id) && parent.getAdapter() != null) { CompoundTag nbt = getTile(x, y, z); if (nbt != null) { if (mutableLoc == null) mutableLoc = new Location(world, 0, 0, 0); @@ -216,7 +216,7 @@ public class BukkitChunk_All extends CharFaweChunk { mutableLoc.setY(y); mutableLoc.setZ(bz + z); synchronized (BukkitChunk_All.this) { - parent.adapter.setBlock(mutableLoc, new BaseBlock(id, combined & 0xF, nbt), false); + parent.getAdapter().setBlock(mutableLoc, new BaseBlock(id, combined & 0xF, nbt), false); } continue; } @@ -276,10 +276,10 @@ public class BukkitChunk_All extends CharFaweChunk { int x = cacheX[j]; int z = cacheZ[j]; int y = cacheY[j]; - if (FaweCache.hasNBT(id) && parent.adapter != null) { + if (FaweCache.hasNBT(id) && parent.getAdapter() != null) { CompoundTag tile = getTile(x, y, z); if (tile != null) { - parent.adapter.setBlock(new Location(world, bx + x, y, bz + z), new BaseBlock(id, combined & 0xF, tile), false); + parent.getAdapter().setBlock(new Location(world, bx + x, y, bz + z), new BaseBlock(id, combined & 0xF, tile), false); break; } } diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitQueue_0.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitQueue_0.java index 8ae633d4..993b4b45 100644 --- a/bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitQueue_0.java +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitQueue_0.java @@ -12,11 +12,13 @@ import com.boydti.fawe.object.RunnableVal; import com.boydti.fawe.object.visitor.FaweChunkVisitor; import com.boydti.fawe.util.MathMan; import com.boydti.fawe.util.TaskManager; +import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.bukkit.WorldEditPlugin; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; import com.sk89q.worldedit.world.biome.BaseBiome; import java.io.File; import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Collection; import java.util.Map; @@ -37,9 +39,10 @@ import org.bukkit.event.world.WorldInitEvent; public abstract class BukkitQueue_0 extends NMSMappedFaweQueue implements Listener { - public static BukkitImplAdapter adapter; - public static Method methodToNative; - public static Method methodFromNative; + private static BukkitImplAdapter adapter; + private static FaweAdapter_All backupAdaper; + private static Method methodToNative; + private static Method methodFromNative; private static boolean setupAdapter = false; public BukkitQueue_0(final com.sk89q.worldedit.world.World world) { @@ -62,9 +65,38 @@ public abstract class BukkitQueue_0 extends NMSMa public static BukkitImplAdapter getAdapter() { if (adapter == null) setupAdapter(null); + if (adapter == null) return backupAdaper; return adapter; } + public static Tag toNative(Object tag) { + BukkitImplAdapter adapter = getAdapter(); + if (adapter == null) { + if (backupAdaper != null) return backupAdaper.toNative(tag); + return null; + } + try { + return (Tag) methodToNative.invoke(adapter, tag); + } catch (InvocationTargetException | IllegalAccessException e) { + e.printStackTrace(); + } + return null; + } + + public static Object fromNative(Tag tag) { + BukkitImplAdapter adapter = getAdapter(); + if (adapter == null) { + if (backupAdaper != null) return backupAdaper.fromNative(tag); + return null; + } + try { + return methodFromNative.invoke(adapter, tag); + } catch (InvocationTargetException | IllegalAccessException e) { + e.printStackTrace(); + } + return null; + } + @Override public File getSaveFolder() { return new File(Bukkit.getWorldContainer(), getWorldName() + File.separator + "region"); @@ -152,37 +184,46 @@ public abstract class BukkitQueue_0 extends NMSMa public static void setupAdapter(BukkitImplAdapter adapter) { try { - if (setupAdapter == (setupAdapter = true)) { + if (adapter == null && setupAdapter == (setupAdapter = true)) { return; } WorldEditPlugin instance = (WorldEditPlugin) Bukkit.getPluginManager().getPlugin("WorldEdit"); Field fieldAdapter = WorldEditPlugin.class.getDeclaredField("bukkitAdapter"); fieldAdapter.setAccessible(true); - if ((BukkitQueue_0.adapter = adapter) != null) { + if (adapter != null) { + BukkitQueue_0.adapter = adapter; fieldAdapter.set(instance, adapter); } else { BukkitQueue_0.adapter = adapter = (BukkitImplAdapter) fieldAdapter.get(instance); } - for (Method method : adapter.getClass().getDeclaredMethods()) { - switch (method.getName()) { - case "toNative": - methodToNative = method; - methodToNative.setAccessible(true); - break; - case "fromNative": - methodFromNative = method; - methodFromNative.setAccessible(true); - break; + if (adapter != null) { + for (Method method : adapter.getClass().getDeclaredMethods()) { + switch (method.getName()) { + case "toNative": + methodToNative = method; + methodToNative.setAccessible(true); + break; + case "fromNative": + methodFromNative = method; + methodFromNative.setAccessible(true); + break; + } + } + } + return; + } catch (Throwable ignore) { + ignore.printStackTrace(); + } + if (BukkitQueue_0.adapter == null) { + if (backupAdaper == null) { + try { + backupAdaper = new FaweAdapter_All(); + Fawe.debug("Native adapter failed. Backup adapter is functional."); + } catch (Throwable ignore) { + Fawe.debug("Native and backup adapter failed! (Try updating the plugin)"); + ignore.printStackTrace(); } } - } catch (Throwable e) { - setupAdapter = false; - Fawe.debug("====== NO NATIVE WORLDEDIT ADAPTER ======"); - e.printStackTrace(); - Fawe.debug("Try updating WorldEdit: "); - Fawe.debug(" - http://builds.enginehub.org/job/worldedit?branch=master"); - Fawe.debug("See also: http://wiki.sk89q.com/wiki/WorldEdit/Bukkit_adapters"); - Fawe.debug("========================================="); } } diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitQueue_All.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitQueue_All.java index 7c846efc..c536f8dc 100644 --- a/bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitQueue_All.java +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitQueue_All.java @@ -3,11 +3,20 @@ package com.boydti.fawe.bukkit.v0; import com.boydti.fawe.FaweCache; import com.boydti.fawe.config.Settings; import com.boydti.fawe.object.FaweChunk; +import com.boydti.fawe.object.RegionWrapper; +import com.boydti.fawe.object.RunnableVal; import com.boydti.fawe.util.MathMan; +import com.boydti.fawe.util.ReflectionUtils; +import com.boydti.fawe.util.SetQueue; +import com.boydti.fawe.util.TaskManager; import com.sk89q.jnbt.CompoundTag; import com.sk89q.worldedit.blocks.BaseBlock; +import java.io.File; +import java.io.RandomAccessFile; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.util.ArrayDeque; +import java.util.Map; import org.bukkit.Chunk; import org.bukkit.ChunkSnapshot; import org.bukkit.Location; @@ -37,6 +46,138 @@ public class BukkitQueue_All extends BukkitQueue_0 classRegionFileCache; + private static Class classRegionFile; + private static Class classCraftChunk; + private static Class classCraftWorld; + private static Class classNMSChunk; + private static Class classNMSWorld; + private static Class classChunkProviderServer; + private static Class classIChunkLoader; + private static Class classChunkRegionLoader; + private static Class classIChunkProvider; + private static Method methodGetHandleChunk; + private static Method methodGetHandleWorld; + private static Method methodFlush; + private static Method methodNeedsSaving; + private static Field fieldChunkProvider; + private static Field fieldChunkLoader; + private static Field fieldRegionMap; + private static Field fieldRegionRAF; + + static { + try { + ReflectionUtils.init(); + classRegionFileCache = ReflectionUtils.getNmsClass("RegionFileCache"); + classRegionFile = ReflectionUtils.getNmsClass("RegionFile"); + classCraftChunk = ReflectionUtils.getCbClass("CraftChunk"); + classNMSChunk = ReflectionUtils.getNmsClass("Chunk"); + classCraftWorld = ReflectionUtils.getCbClass("CraftWorld"); + classNMSWorld = ReflectionUtils.getNmsClass("World"); + classChunkProviderServer = ReflectionUtils.getNmsClass("ChunkProviderServer"); + classIChunkProvider = ReflectionUtils.getNmsClass("IChunkProvider"); + classIChunkLoader = ReflectionUtils.getNmsClass("IChunkLoader"); + classChunkRegionLoader = ReflectionUtils.getNmsClass("ChunkRegionLoader"); + + methodGetHandleChunk = ReflectionUtils.setAccessible(classCraftChunk.getDeclaredMethod("getHandle")); + methodGetHandleWorld = ReflectionUtils.setAccessible(classCraftWorld.getDeclaredMethod("getHandle")); + methodFlush = ReflectionUtils.findMethod(classChunkRegionLoader, boolean.class); + methodNeedsSaving = ReflectionUtils.findMethod(classNMSChunk, boolean.class, boolean.class); + + fieldChunkProvider = ReflectionUtils.findField(classNMSWorld, classIChunkProvider); + fieldChunkLoader = ReflectionUtils.findField(classChunkProviderServer, classIChunkLoader); + + fieldRegionMap = ReflectionUtils.findField(classRegionFileCache, Map.class); + fieldRegionRAF = ReflectionUtils.findField(classRegionFile, RandomAccessFile.class); + } catch (Throwable ignore) { + ignore.printStackTrace(); + } + } + + @Override + public boolean setMCA(int mcaX, int mcaZ, RegionWrapper allowed, Runnable whileLocked, boolean load) { + if (classRegionFileCache == null) { + return super.setMCA(mcaX, mcaZ, allowed, whileLocked, load); + } + TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(Boolean value) { + long start = System.currentTimeMillis(); + long last = start; + synchronized (classRegionFileCache) { + try { + World world = getWorld(); + boolean autoSave = world.isAutoSave(); + + if (world.getKeepSpawnInMemory()) world.setKeepSpawnInMemory(false); + + ArrayDeque unloaded = null; + if (load) { + int bcx = mcaX << 5; + int bcz = mcaZ << 5; + int tcx = bcx + 31; + int tcz = bcz + 31; + for (Chunk chunk : world.getLoadedChunks()) { + int cx = chunk.getX(); + int cz = chunk.getZ(); + if (cx >= bcx && cx <= tcx && cz >= bcz && cz <= tcz) { + Object nmsChunk = methodGetHandleChunk.invoke(chunk); + boolean mustSave = (boolean) methodNeedsSaving.invoke(nmsChunk, false); + chunk.unload(mustSave, false); + if (unloaded == null) unloaded = new ArrayDeque(); + unloaded.add(chunk); + } + } + } else { + world.save(); + } + + Object nmsWorld = methodGetHandleWorld.invoke(world); + Object chunkProviderServer = fieldChunkProvider.get(nmsWorld); + Object chunkRegionLoader = fieldChunkLoader.get(chunkProviderServer); + while ((boolean) methodFlush.invoke(chunkRegionLoader)); + + if (unloaded != null) { + Map regionMap = (Map) fieldRegionMap.get(null); + File file = new File(world.getWorldFolder(), "region" + File.separator + "r." + mcaX + "." + mcaZ + ".mca"); + Object regionFile = regionMap.remove(file); + if (regionFile != null) { + RandomAccessFile raf = (RandomAccessFile) fieldRegionRAF.get(regionFile); + raf.close(); + } + } + + whileLocked.run(); + + if (load && unloaded != null) { + final ArrayDeque finalUnloaded = unloaded; + TaskManager.IMP.async(new Runnable() { + @Override + public void run() { + for (Chunk chunk : finalUnloaded) { + int cx = chunk.getX(); + int cz = chunk.getZ(); + if (world.isChunkLoaded(cx, cz)) continue; + SetQueue.IMP.addTask(() -> { + world.loadChunk(chunk.getX(), chunk.getZ(), false); + world.refreshChunk(chunk.getX(), chunk.getZ()); + }); + + } + } + }); + // load chunks + + } + } catch (Throwable e) { + e.printStackTrace(); + } + } + } + }); + return true; + } + @Override public void setHeightMap(FaweChunk chunk, byte[] heightMap) { // Not supported @@ -69,7 +210,7 @@ public class BukkitQueue_All extends BukkitQueue_0 classCraftBlock; + private final Method biomeToBiomeBase; + private final Class classBiomeBase; + private final Method biomeBaseToTypeId; + private final Method getBiome; + private final Method biomeBaseToBiome; + private final Class classCraftWorld; + private final Method getHandleWorld; + private final Class classWorld; + private final Method getTileEntity1; + private final Method getTileEntity2; + private final Class classNBTTagCompound; + private final Constructor newNBTTagCompound; + private final Class classTileEntity; + private final Class classCraftEntity; + private final Method getHandleEntity; + private final Class classNBTTagInt; + private final Class classNBTBase; + private final Constructor newNBTTagInt; + private final Method setNBTTagCompound; + private Class classEntity; + private Method getBukkitEntity; + private Method addEntity; + private Method setLocation; + private Class classEntityTypes; + private Method getEntityId; + private Method createEntityFromId; + private Method readTagIntoEntity; + private Method readEntityIntoTag; + private Constructor newMinecraftKey; + private Class classMinecraftKey; + private Method readTagIntoTileEntity; + private Method readTileEntityIntoTag; + private Class classBlockPosition; + private Constructor newBlockPosition; + + private Map, NMSTagConstructor> WEToNMS = new ConcurrentHashMap<>(); + private Map NMSToWE = new ConcurrentHashMap<>(); + private Map, Integer> TagToId = new ConcurrentHashMap<>(); + + public FaweAdapter_All() throws Throwable { + ReflectionUtils.init(); + classCraftWorld = ReflectionUtils.getCbClass("CraftWorld"); + classCraftBlock = ReflectionUtils.getCbClass("block.CraftBlock"); + classCraftEntity = ReflectionUtils.getCbClass("entity.CraftEntity"); + classBiomeBase = ReflectionUtils.getNmsClass("BiomeBase"); + classWorld = ReflectionUtils.getNmsClass("World"); + classTileEntity = ReflectionUtils.getNmsClass("TileEntity"); + + biomeToBiomeBase = ReflectionUtils.setAccessible(classCraftBlock.getDeclaredMethod("biomeToBiomeBase", Biome.class)); + biomeBaseToBiome = ReflectionUtils.setAccessible(classCraftBlock.getDeclaredMethod("biomeBaseToBiome", classBiomeBase)); + getBiome = ReflectionUtils.setAccessible(classBiomeBase.getDeclaredMethod("getBiome", int.class)); + biomeBaseToTypeId = ReflectionUtils.findMethod(classBiomeBase, int.class, classBiomeBase); + getHandleWorld = ReflectionUtils.setAccessible(classCraftWorld.getDeclaredMethod("getHandle")); + getHandleEntity = ReflectionUtils.setAccessible(classCraftEntity.getDeclaredMethod("getHandle")); + try { + classBlockPosition = ReflectionUtils.getNmsClass("BlockPosition"); + } catch (Throwable ignore) { + } + if (classBlockPosition != null) { + getTileEntity1 = classWorld.getDeclaredMethod("getTileEntity", classBlockPosition); + getTileEntity2 = null; + newBlockPosition = ReflectionUtils.setAccessible(classBlockPosition.getConstructor(int.class, int.class, int.class)); + } else { + getTileEntity1 = null; + getTileEntity2 = ReflectionUtils.setAccessible(classWorld.getDeclaredMethod("getTileEntity", int.class, int.class, int.class)); + } + + classNBTTagCompound = ReflectionUtils.getNmsClass("NBTTagCompound"); + classNBTBase = ReflectionUtils.getNmsClass("NBTBase"); + classNBTTagInt = ReflectionUtils.getNmsClass("NBTTagInt"); + newNBTTagInt = ReflectionUtils.setAccessible(classNBTTagInt.getConstructor(int.class)); + setNBTTagCompound = ReflectionUtils.setAccessible(classNBTTagCompound.getDeclaredMethod("set", String.class, classNBTBase)); + newNBTTagCompound = ReflectionUtils.setAccessible(classNBTTagCompound.getConstructor()); + try { + readTileEntityIntoTag = ReflectionUtils.setAccessible(classTileEntity.getDeclaredMethod("save", classNBTTagCompound)); + } catch (Throwable ignore) { + readTileEntityIntoTag = ReflectionUtils.findMethod(classTileEntity, classNBTTagCompound, classNBTTagCompound); + if (readTileEntityIntoTag == null) { + readTileEntityIntoTag = ReflectionUtils.findMethod(classTileEntity, 1, Void.TYPE, classNBTTagCompound); + } + } + + + try { + readTagIntoTileEntity = ReflectionUtils.setAccessible(classTileEntity.getDeclaredMethod("load", classNBTTagCompound)); + } catch (Throwable ignore) { + readTagIntoTileEntity = ReflectionUtils.findMethod(classTileEntity, 0, Void.TYPE, classNBTTagCompound); + } + + + List nmsClasses = Arrays.asList("NBTTagCompound", "NBTTagByte", "NBTTagByteArray", "NBTTagDouble", "NBTTagFloat", "NBTTagInt", "NBTTagIntArray", "NBTTagList", "NBTTagEnd", "NBTTagString", "NBTTagShort", "NBTTagLong"); + List> weClasses = Arrays.asList(CompoundTag.class, ByteTag.class, ByteArrayTag.class, DoubleTag.class, FloatTag.class, IntTag.class, IntArrayTag.class, ListTag.class, EndTag.class, StringTag.class, ShortTag.class, LongTag.class); + int[] ids = new int[]{10, 1, 7, 6, 5, 3, 11, 9, 0, 8, 2, 4}; + + int noMods = Modifier.STATIC; + int hasMods = Modifier.PRIVATE; + for (int i = 0; i < nmsClasses.size(); i++) { + Class nmsClass = ReflectionUtils.getNmsClass(nmsClasses.get(i)); + Class weClass = weClasses.get(i); + TagToId.put(weClass, ids[i]); + + Constructor nmsConstructor = ReflectionUtils.setAccessible(nmsClass.getDeclaredConstructor()); + + if (weClass == EndTag.class) { + NMSToWE.put(nmsClass, value -> new EndTag()); + WEToNMS.put(weClass, value -> nmsConstructor.newInstance()); + } else if (weClass == CompoundTag.class) { + Field mapField = ReflectionUtils.findField(nmsClass, Map.class, hasMods, noMods); + Constructor weConstructor = ReflectionUtils.setAccessible(CompoundTag.class.getConstructor(Map.class)); + + NMSToWE.put(nmsClass, value -> { + Map map = (Map) mapField.get(value); + Map weMap = new HashMap(); + for (Map.Entry entry : map.entrySet()) { + weMap.put(entry.getKey(), toNative(entry.getValue())); + } + return new CompoundTag(weMap); + }); + + WEToNMS.put(weClass, value -> { + Map map = ReflectionUtils.getMap(((CompoundTag) value).getValue()); + Object nmsTag = nmsConstructor.newInstance(); + Map nmsMap = (Map) mapField.get(nmsTag); + for (Map.Entry entry : map.entrySet()) { + nmsMap.put(entry.getKey(), fromNative(entry.getValue())); + } + return nmsTag; + }); + } else if (weClass == ListTag.class) { + Field listField = ReflectionUtils.findField(nmsClass, List.class, hasMods, noMods); + Field typeField = ReflectionUtils.findField(nmsClass, byte.class, hasMods, noMods); + Constructor weConstructor = ReflectionUtils.setAccessible(ListTag.class.getConstructor(Class.class, List.class)); + + NMSToWE.put(nmsClass, tag -> { + int type = ((Number) typeField.get(tag)).intValue(); + List list = (List) listField.get(tag); + + Class weType = NBTConstants.getClassFromType(type); + ArrayList weList = new ArrayList<>(); + for (Object nmsTag : list) { + weList.add(toNative(nmsTag)); + } + return new ListTag(weType, weList); + }); + WEToNMS.put(weClass, tag -> { + ListTag lt = (ListTag) tag; + List list = ReflectionUtils.getList(lt.getValue()); + Class type = lt.getType(); + + int typeId = TagToId.get(type); + Object nmsTagList = nmsConstructor.newInstance(); + typeField.set(nmsTagList, (byte) typeId); + ArrayList nmsList = (ArrayList) listField.get(nmsTagList); + for (Tag weTag : list) { + nmsList.add(fromNative(weTag)); + } + return nmsTagList; + }); + } else { + Field typeField = ReflectionUtils.findField(nmsClass, null, hasMods, noMods); + Constructor weConstructor = ReflectionUtils.setAccessible(weClass.getConstructor(typeField.getType())); + + NMSToWE.put(nmsClass, tag -> { + Object value = typeField.get(tag); + return weConstructor.newInstance(value); + }); + + WEToNMS.put(weClass, tag -> { + Object nmsTag = nmsConstructor.newInstance(); + typeField.set(nmsTag, tag.getValue()); + return nmsTag; + }); + } + } + try { + classEntity = ReflectionUtils.getNmsClass("Entity"); + classEntityTypes = ReflectionUtils.getNmsClass("EntityTypes"); + + getBukkitEntity = ReflectionUtils.setAccessible(classEntity.getDeclaredMethod("getBukkitEntity")); + addEntity = ReflectionUtils.setAccessible(classWorld.getDeclaredMethod("addEntity", classEntity)); + setLocation = ReflectionUtils.setAccessible(classEntity.getDeclaredMethod("setLocation", double.class, double.class, double.class, float.class, float.class)); + + try { + classMinecraftKey = ReflectionUtils.getNmsClass("MinecraftKey"); + newMinecraftKey = classMinecraftKey.getConstructor(String.class); + } catch (Throwable ignore) { + } + if (classMinecraftKey != null) { + getEntityId = ReflectionUtils.findMethod(classEntityTypes, classMinecraftKey, classEntity); + createEntityFromId = ReflectionUtils.findMethod(classEntityTypes, classEntity, classMinecraftKey, classWorld); + } else { + getEntityId = ReflectionUtils.findMethod(classEntityTypes, String.class, classEntity); + createEntityFromId = ReflectionUtils.findMethod(classEntityTypes, classEntity, String.class, classWorld); + } + + noMods = Modifier.ABSTRACT | Modifier.PROTECTED | Modifier.PRIVATE; + try { + readEntityIntoTag = classEntity.getDeclaredMethod("save", classNBTTagCompound); + } catch (Throwable ignore) { + readEntityIntoTag = ReflectionUtils.findMethod(classEntity, classNBTTagCompound, classNBTTagCompound); + if (readEntityIntoTag == null) { + readEntityIntoTag = ReflectionUtils.findMethod(classEntity, 0, 0, noMods, Void.TYPE, classNBTTagCompound); + } + } + ReflectionUtils.setAccessible(readEntityIntoTag); + readTagIntoEntity = ReflectionUtils.findMethod(classEntity, 1, 0, noMods, Void.TYPE, classNBTTagCompound); + if (readTagIntoEntity == null) { + readTagIntoEntity = ReflectionUtils.findMethod(classEntity, 0, 0, noMods, Void.TYPE, classNBTTagCompound); + } + } catch (Throwable e) { + e.printStackTrace(); + classEntity = null; + } + } + + @Nullable + @Override + public BaseEntity getEntity(Entity entity) { + try { + if (classEntity == null) return null; + Object nmsEntity = getHandleEntity.invoke(entity); + + String id = getEntityId(nmsEntity); + + if (id != null) { + Object tag = newNBTTagCompound.newInstance(); + readEntityIntoTag.invoke(nmsEntity, tag); + return new BaseEntity(id, (CompoundTag) toNative(tag)); + } + } catch (Throwable e) { + throw new RuntimeException(e); + } + return null; + } + + private String getEntityId(Object entity) throws InvocationTargetException, IllegalAccessException { + Object res = getEntityId.invoke(null, entity); + return res == null ? null : res.toString(); + } + + private Object createEntityFromId(String id, Object world) throws InvocationTargetException, IllegalAccessException, InstantiationException { + if (classMinecraftKey != null) { + Object key = newMinecraftKey.newInstance(id); + return createEntityFromId.invoke(null, key, world); + } else { + return createEntityFromId.invoke(null, id, world); + } + } + + @Nullable + @Override + public Entity createEntity(Location location, BaseEntity state) { + try { + if (classEntity == null) return null; + World world = location.getWorld(); + Object nmsWorld = getHandleWorld.invoke(world); + + Object createdEntity = createEntityFromId(state.getTypeId(), nmsWorld); + + if (createdEntity != null) { + CompoundTag nativeTag = state.getNbtData(); + Map rawMap = ReflectionUtils.getMap(nativeTag.getValue()); + for (String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) { + rawMap.remove(name); + } + if (nativeTag != null) { + Object tag = fromNative(nativeTag); + readTagIntoEntity.invoke(createdEntity, tag); + } + + setLocation.invoke(createdEntity, location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); + + addEntity.invoke(nmsWorld, createdEntity, CreatureSpawnEvent.SpawnReason.CUSTOM); + return (Entity) getBukkitEntity.invoke(createdEntity); + } + } catch (Throwable e) { + throw new RuntimeException(e); + } + return null; + } + + public Tag toNative(Object nmsTag) { + try { + return NMSToWE.get(nmsTag.getClass()).construct(nmsTag); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public Object fromNative(Tag tag) { + try { + return WEToNMS.get(tag.getClass()).construct(tag); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public int getBlockId(Material material) { + return material.getId(); + } + + @Override + public Material getMaterial(int id) { + return Material.getMaterial(id); + } + + @Override + public int getBiomeId(Biome biome) { + try { + Object biomeBase = biomeToBiomeBase.invoke(null, biome); + if (biomeBase != null) return (int) biomeBaseToTypeId.invoke(null, biomeBase); + } catch (Throwable e) { + throw new RuntimeException(e); + } + return 0; + } + + @Override + public Biome getBiome(int id) { + try { + Object biomeBase = getBiome.invoke(null, id); + if (biomeBase != null) return (Biome) biomeBaseToBiome.invoke(null, biomeBase); + } catch (Throwable e) { + throw new RuntimeException(e); + } + return Biome.OCEAN; + } + + @Override + public BaseBlock getBlock(Location location) { + try { + World craftWorld = location.getWorld(); + int x = location.getBlockX(); + int y = location.getBlockY(); + int z = location.getBlockZ(); + + org.bukkit.block.Block bukkitBlock = location.getBlock(); + BaseBlock block = FaweCache.getBlock(bukkitBlock.getTypeId(), bukkitBlock.getData()); + + // Read the NBT data + Object nmsWorld = getHandleWorld.invoke(craftWorld); + Object tileEntity = getTileEntity(nmsWorld, x, y, z); + + if (tileEntity != null) { + block = new BaseBlock(block); + Object tag = newNBTTagCompound.newInstance(); + readTileEntityIntoTag.invoke(tileEntity, tag); + block.setNbtData((CompoundTag) toNative(tag)); + } + return block; + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + public Object getTileEntity(Object nmsWorld, int x, int y, int z) { + try { + if (getTileEntity1 != null) { + Object pos = newBlockPosition.newInstance(x, y, z); + return getTileEntity1.invoke(nmsWorld, pos); + } else { + return getTileEntity2.invoke(nmsWorld, x, y, z); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean setBlock(Location location, BaseBlock block, boolean notifyAndLight) { + World craftWorld = location.getWorld(); + int x = location.getBlockX(); + int y = location.getBlockY(); + int z = location.getBlockZ(); + + boolean changed = location.getBlock().setTypeIdAndData(block.getId(), (byte) block.getData(), notifyAndLight); + + CompoundTag nativeTag = block.getNbtData(); + if (nativeTag != null) { + try { + Object nmsWorld = getHandleWorld.invoke(craftWorld); + Object tileEntity = getTileEntity(nmsWorld, x, y, z); + if (tileEntity != null) { + Object tag = fromNative(nativeTag); + + setNBTTagCompound.invoke(tag, "x", newNBTTagInt.newInstance(x)); + setNBTTagCompound.invoke(tag, "y", newNBTTagInt.newInstance(y)); + setNBTTagCompound.invoke(tag, "z", newNBTTagInt.newInstance(z)); + readTagIntoTileEntity.invoke(tileEntity, tag); // Load data + } + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + return changed; + } + + private interface NMSTagConstructor { + Object construct(Tag value) throws Exception; + } + + private interface WETagConstructor { + Tag construct(Object value) throws Exception; + } +} diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_10/BukkitChunk_1_10.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_10/BukkitChunk_1_10.java index 4e71d6bf..7926218c 100644 --- a/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_10/BukkitChunk_1_10.java +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_10/BukkitChunk_1_10.java @@ -198,7 +198,7 @@ public class BukkitChunk_1_10 extends CharFaweChunk { if (id != null) { NBTTagCompound tag = new NBTTagCompound(); ent.e(tag); // readEntityIntoTag - CompoundTag nativeTag = (CompoundTag) getParent().methodToNative.invoke(getParent().adapter, tag); + CompoundTag nativeTag = (CompoundTag) getParent().toNative(tag); Map map = ReflectionUtils.getMap(nativeTag.getValue()); map.put("Id", new StringTag(id)); setEntity(nativeTag); @@ -321,7 +321,7 @@ public class BukkitChunk_1_10 extends CharFaweChunk { entityTagMap.put("UUIDMost", new LongTag(uuid.getMostSignificantBits())); entityTagMap.put("UUIDLeast", new LongTag(uuid.getLeastSignificantBits())); if (nativeTag != null) { - NBTTagCompound tag = (NBTTagCompound) BukkitQueue_1_10.methodFromNative.invoke(BukkitQueue_1_10.adapter, nativeTag); + NBTTagCompound tag = (NBTTagCompound) BukkitQueue_1_10.fromNative(nativeTag); for (String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) { tag.remove(name); } @@ -474,7 +474,7 @@ public class BukkitChunk_1_10 extends CharFaweChunk { net.minecraft.server.v1_10_R1.BlockPosition pos = new net.minecraft.server.v1_10_R1.BlockPosition(x, y, z); // Set pos net.minecraft.server.v1_10_R1.TileEntity tileEntity = nmsWorld.getTileEntity(pos); if (tileEntity != null) { - net.minecraft.server.v1_10_R1.NBTTagCompound tag = (net.minecraft.server.v1_10_R1.NBTTagCompound) com.boydti.fawe.bukkit.v1_10.BukkitQueue_1_10.methodFromNative.invoke(com.boydti.fawe.bukkit.v1_10.BukkitQueue_1_10.adapter, nativeTag); + net.minecraft.server.v1_10_R1.NBTTagCompound tag = (net.minecraft.server.v1_10_R1.NBTTagCompound) com.boydti.fawe.bukkit.v1_10.BukkitQueue_1_10.fromNative(nativeTag); tag.set("x", new NBTTagInt(x)); tag.set("y", new NBTTagInt(y)); tag.set("z", new NBTTagInt(z)); diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_10/BukkitQueue_1_10.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_10/BukkitQueue_1_10.java index 03083a8e..e140a6e8 100644 --- a/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_10/BukkitQueue_1_10.java +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_10/BukkitQueue_1_10.java @@ -140,7 +140,7 @@ public class BukkitQueue_1_10 extends BukkitQueue_0 map = ReflectionUtils.getMap(nativeTag.getValue()); map.put("Id", new StringTag(id)); previous.setEntity(nativeTag); @@ -829,7 +829,7 @@ public class BukkitQueue_1_10 extends BukkitQueue_0 map = ReflectionUtils.getMap(nativeTag.getValue()); map.put("Id", new StringTag(id)); setEntity(nativeTag); @@ -330,7 +330,7 @@ public class BukkitChunk_1_11 extends CharFaweChunk map = ReflectionUtils.getMap(nativeTag.getValue()); map.put("Id", new StringTag(id)); previous.setEntity(nativeTag); @@ -831,7 +831,7 @@ public class BukkitQueue_1_11 extends BukkitQueue_0 { } public boolean storeEntity(Entity ent) throws InvocationTargetException, IllegalAccessException { - if (ent instanceof EntityPlayer || BukkitQueue_0.adapter == null) { + if (ent instanceof EntityPlayer || BukkitQueue_0.getAdapter() == null) { return false; } int x = ((int) Math.round(ent.locX) & 15); @@ -90,7 +90,7 @@ public class BukkitChunk_1_12 extends CharFaweChunk { if (id != null) { NBTTagCompound tag = new NBTTagCompound(); ent.save(tag); // readEntityIntoTag - CompoundTag nativeTag = (CompoundTag) BukkitQueue_0.methodToNative.invoke(getParent().adapter, tag); + CompoundTag nativeTag = (CompoundTag) BukkitQueue_0.toNative(tag); Map map = ReflectionUtils.getMap(nativeTag.getValue()); map.put("Id", new StringTag(id)); setEntity(nativeTag); @@ -330,7 +330,7 @@ public class BukkitChunk_1_12 extends CharFaweChunk { entityTagMap.put("UUIDMost", new LongTag(uuid.getMostSignificantBits())); entityTagMap.put("UUIDLeast", new LongTag(uuid.getLeastSignificantBits())); if (nativeTag != null) { - NBTTagCompound tag = (NBTTagCompound) BukkitQueue_1_12.methodFromNative.invoke(BukkitQueue_1_12.adapter, nativeTag); + NBTTagCompound tag = (NBTTagCompound) BukkitQueue_1_12.fromNative(nativeTag); for (String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) { tag.remove(name); } @@ -484,7 +484,7 @@ public class BukkitChunk_1_12 extends CharFaweChunk { BlockPosition pos = new BlockPosition(x, y, z); // Set pos TileEntity tileEntity = nmsWorld.getTileEntity(pos); if (tileEntity != null) { - NBTTagCompound tag = (NBTTagCompound) BukkitQueue_1_12.methodFromNative.invoke(BukkitQueue_1_12.adapter, nativeTag); + NBTTagCompound tag = (NBTTagCompound) BukkitQueue_1_12.fromNative(nativeTag); tag.set("x", new NBTTagInt(x)); tag.set("y", new NBTTagInt(y)); tag.set("z", new NBTTagInt(z)); diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_12/BukkitQueue_1_12.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_12/BukkitQueue_1_12.java index 46252ee0..186c48c1 100644 --- a/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_12/BukkitQueue_1_12.java +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_12/BukkitQueue_1_12.java @@ -156,7 +156,7 @@ public class BukkitQueue_1_12 extends BukkitQueue_0 map = ReflectionUtils.getMap(nativeTag.getValue()); map.put("Id", new StringTag(id)); previous.setEntity(nativeTag); @@ -846,7 +846,7 @@ public class BukkitQueue_1_12 extends BukkitQueue_0 { Entity entity = EntityTypes.createEntityByName(id, nmsWorld); if (entity != null) { if (nativeTag != null) { - NBTTagCompound tag = (NBTTagCompound) BukkitQueue17.methodFromNative.invoke(BukkitQueue17.adapter, nativeTag); + NBTTagCompound tag = (NBTTagCompound) BukkitQueue17.fromNative(nativeTag); for (String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) { tag.remove(name); } @@ -446,7 +446,7 @@ public class BukkitChunk_1_7 extends CharFaweChunk { int z = (blockHash >> 8 & 0xF) + bz; TileEntity tileEntity = nmsWorld.getTileEntity(x, y, z); if (tileEntity != null) { - NBTTagCompound tag = (NBTTagCompound) BukkitQueue17.methodFromNative.invoke(BukkitQueue17.adapter, nativeTag); + NBTTagCompound tag = (NBTTagCompound) BukkitQueue17.fromNative(nativeTag); tag.set("x", new NBTTagInt(x)); tag.set("y", new NBTTagInt(y)); tag.set("z", new NBTTagInt(z)); diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_7/BukkitQueue17.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_7/BukkitQueue17.java index 6c1acb77..56ad65ca 100644 --- a/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_7/BukkitQueue17.java +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_7/BukkitQueue17.java @@ -253,7 +253,7 @@ public class BukkitQueue17 extends BukkitQueue_0 map = ReflectionUtils.getMap(nativeTag.getValue()); map.put("Id", new StringTag(id)); previous.setEntity(nativeTag); @@ -436,7 +436,7 @@ public class BukkitQueue17 extends BukkitQueue_0 { Entity entity = EntityTypes.createEntityByName(id, nmsWorld); if (entity != null) { if (nativeTag != null) { - NBTTagCompound tag = (NBTTagCompound) BukkitQueue18R3.methodFromNative.invoke(BukkitQueue18R3.adapter, nativeTag); + NBTTagCompound tag = (NBTTagCompound) BukkitQueue18R3.fromNative(nativeTag); for (String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) { tag.remove(name); } @@ -307,7 +307,7 @@ public class BukkitChunk_1_8 extends CharFaweChunk { BlockPosition pos = new BlockPosition(x, y, z); // Set pos TileEntity tileEntity = nmsWorld.getTileEntity(pos); if (tileEntity != null) { - NBTTagCompound tag = (NBTTagCompound) BukkitQueue18R3.methodFromNative.invoke(BukkitQueue18R3.adapter, nativeTag); + NBTTagCompound tag = (NBTTagCompound) BukkitQueue18R3.fromNative(nativeTag); tag.set("x", new NBTTagInt(x)); tag.set("y", new NBTTagInt(y)); tag.set("z", new NBTTagInt(z)); diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_8/BukkitQueue18R3.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_8/BukkitQueue18R3.java index e882142c..f194b324 100644 --- a/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_8/BukkitQueue18R3.java +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_8/BukkitQueue18R3.java @@ -251,7 +251,7 @@ public class BukkitQueue18R3 extends BukkitQueue_0 map = ReflectionUtils.getMap(nativeTag.getValue()); map.put("Id", new StringTag(id)); previous.setEntity(nativeTag); @@ -426,7 +426,7 @@ public class BukkitQueue18R3 extends BukkitQueue_0 { entityTagMap.put("UUIDMost", new LongTag(uuid.getMostSignificantBits())); entityTagMap.put("UUIDLeast", new LongTag(uuid.getLeastSignificantBits())); if (nativeTag != null) { - NBTTagCompound tag = (NBTTagCompound) BukkitQueue_1_9_R1.methodFromNative.invoke(BukkitQueue_1_9_R1.adapter, nativeTag); + NBTTagCompound tag = (NBTTagCompound) BukkitQueue_1_9_R1.fromNative(nativeTag); for (String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) { tag.remove(name); } @@ -426,7 +426,7 @@ public class BukkitChunk_1_9 extends CharFaweChunk { BlockPosition pos = new BlockPosition(x, y, z); // Set pos TileEntity tileEntity = nmsWorld.getTileEntity(pos); if (tileEntity != null) { - NBTTagCompound tag = (NBTTagCompound) BukkitQueue_1_9_R1.methodFromNative.invoke(BukkitQueue_1_9_R1.adapter, nativeTag); + NBTTagCompound tag = (NBTTagCompound) BukkitQueue_1_9_R1.fromNative(nativeTag); tag.set("x", new NBTTagInt(x)); tag.set("y", new NBTTagInt(y)); tag.set("z", new NBTTagInt(z)); diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_9/BukkitQueue_1_9_R1.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_9/BukkitQueue_1_9_R1.java index a29b49aa..0b474520 100644 --- a/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_9/BukkitQueue_1_9_R1.java +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_9/BukkitQueue_1_9_R1.java @@ -122,9 +122,9 @@ public class BukkitQueue_1_9_R1 extends BukkitQueue_0 map = ReflectionUtils.getMap(nativeTag.getValue()); map.put("Id", new StringTag(id)); previous.setEntity(nativeTag); @@ -784,7 +784,7 @@ public class BukkitQueue_1_9_R1 extends BukkitQueue_0 clazz, final Class type, int hasMods, int noMods) { + for (Field field : clazz.getDeclaredFields()) { + if (type == null || field.getType() == type) { + int mods = field.getModifiers(); + if ((mods & hasMods) == hasMods && (mods & noMods) == 0) { + return setAccessible(field); + } + } + } + return null; + } + + public static Field findField(final Class clazz, final Class type) { + for (Field field : clazz.getDeclaredFields()) { + if (field.getType() == type) { + return setAccessible(field); + } + } + return null; + } + + public static Method findMethod(final Class clazz, final Class returnType, Class... params) { + return findMethod(clazz, 0, returnType, params); + } + + public static Method findMethod(final Class clazz, int index, int hasMods, int noMods, final Class returnType, Class... params) { + outer: + for (Method method : sortMethods(clazz.getDeclaredMethods())) { + if (returnType == null || method.getReturnType() == returnType) { + Class[] mp = method.getParameterTypes(); + int mods = method.getModifiers(); + if ((mods & hasMods) != hasMods || (mods & noMods) != 0) continue; + if (params == null) { + if (index-- == 0) return setAccessible(method); + else { + continue; + } + } + if (mp.length == params.length) { + for (int i = 0; i < mp.length; i++) { + if (mp[i] != params[i]) continue outer; + } + if (index-- == 0) return setAccessible(method); + else { + continue; + } + } + } + } + return null; + } + + private static Method[] sortMethods(Method[] methods) { + Arrays.sort(methods, (o1, o2) -> o1.getName().compareTo(o2.getName())); + return methods; + } + + public static Method findMethod(final Class clazz, int index, final Class returnType, Class... params) { + return findMethod(clazz, index, 0, 0, returnType, params); + } + + public static T setAccessible(final T ao) { + ao.setAccessible(true); + return ao; + } + @SuppressWarnings("unchecked") public static T getField(final Field field, final Object instance) { if (field == null) {