package me.yapperyapps.MainPackage.VersionClasses; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.block.Block; import com.google.common.collect.ImmutableMap; import me.yapperyapps.MainPackage.MinionMain; public class NMSReflection { private MinionMain pl = MinionMain.getInstance(); private static NMSReflection nmsUtils = getInstance(); public static NMSReflection getInstance() { if (nmsUtils == null) nmsUtils = new NMSReflection(); return nmsUtils; } private boolean isLegacy = false; private boolean isAbove16 = false; private Class NMSBlockData, NMSCraftWorld, NMSBlockPosition, NMSCraftMagicNumbers, NMSBlock, BlockStateEnum, BlockStateInteger, BlockStateDirection; private Method getHandle, CMNgetBlock, NMSgetBlock, CMNgetMaterial, BlockStateMethod; private Map NMSgetType = new HashMap<>(); private Constructor blockPosConstructor = null; private String serverVersion; private Map generatedBlockData = new HashMap<>(); private Map craftWorlds = new HashMap<>(); private NMSReflection() { pl.getLogger().info("NMS methods are being set..."); String version = Bukkit.getServer().getClass().getPackage().getName(); version = version.substring(version.lastIndexOf(".") + 1, version.length()); serverVersion = Bukkit.getServer().getVersion().replace("\\(", "").split("MC: ")[1].replace(")", ""); List legacyVersions = Arrays.asList("1_8", "1_9", "1_10", "1_11", "1_12"); isLegacy = legacyVersions.contains(serverVersion.replaceFirst("\\.", "_").split("\\.")[0]); if(serverVersion.replaceFirst("\\.", "_").split("\\.")[0].contains("17")) {isAbove16 = true;} try { NMSBlockData = isAbove16 ? Class.forName("net.minecraft.world.level.block.state.IBlockData") : Class.forName("net.minecraft.server." + version + ".IBlockData"); NMSBlock = isAbove16 ? Class.forName("net.minecraft.world.level.block.Block") : Class.forName("net.minecraft.server." + version + ".Block"); NMSCraftWorld = Class.forName("org.bukkit.craftbukkit." + version + ".CraftWorld"); getHandle = NMSCraftWorld.getDeclaredMethod("getHandle", new Class[]{}); NMSBlockPosition = isAbove16 ? Class.forName("net.minecraft.core.BlockPosition") : Class.forName("net.minecraft.server." + version + ".BlockPosition"); NMSCraftMagicNumbers = Class.forName("org.bukkit.craftbukkit." + version + ".util.CraftMagicNumbers"); CMNgetBlock = NMSCraftMagicNumbers.getMethod("getBlock", Material.class); NMSgetBlock = NMSBlockData.getMethod("getBlock"); CMNgetMaterial = NMSCraftMagicNumbers.getMethod("getMaterial", NMSBlock); blockPosConstructor = NMSBlockPosition.getConstructor(double.class, double.class, double.class); BlockStateMethod = getBlockStateMethod(); BlockStateEnum = isAbove16 ? Class.forName("net.minecraft.world.level.block.state.properties.BlockStateEnum") : Class.forName("net.minecraft.server." + version + ".BlockStateEnum"); BlockStateInteger = isAbove16 ? Class.forName("net.minecraft.world.level.block.state.properties.BlockStateInteger") : Class.forName("net.minecraft.server." + version + ".BlockStateInteger"); BlockStateDirection = isAbove16 ? Class.forName("net.minecraft.world.level.block.state.properties.BlockStateDirection") : Class.forName("net.minecraft.server." + version + ".BlockStateDirection"); }catch (Exception e) { pl.getLogger().warning("An error has occured while loading NMS classes/methods. [NMS-Reflection]"); e.printStackTrace(); } } public boolean isLegacyVersion() { return isLegacy; } public Object getCraftWorld(Location loc) { try { String worldName = loc.getWorld().getName(); if(craftWorlds.containsKey(worldName)) { return craftWorlds.get(worldName); }else { Object craftWorld = getHandle.invoke(NMSCraftWorld.cast(loc.getWorld())); craftWorlds.put(worldName, craftWorld); return craftWorld; } } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { e.printStackTrace(); return null; } } public Method getNMSTypeMethod(Object craftworld) { if(NMSgetType.containsKey(craftworld)) return NMSgetType.get(craftworld); try { Method m = craftworld.getClass().getMethod("getType", NMSBlockPosition); NMSgetType.put(craftworld, m); return m; } catch (NoSuchMethodException | SecurityException e) { e.printStackTrace(); } return null; } public Object getBlockData(Location loc) { try { Object craftWorld = getCraftWorld(loc); Object blockPos = getBlockPosition(loc); return getNMSTypeMethod(craftWorld).invoke(craftWorld, blockPos); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { return null; } } public Object getBlockData(Block b) { try { Class blockClass = b.getClass(); Method getBlockData = blockClass.getMethod("getBlockData"); return getBlockData.invoke(b); } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { e.printStackTrace(); return null; } } public Method getBlockStateMethod() { Method m = null; try { switch(serverVersion.replaceFirst("\\.", "_").split("\\.")[0]) { case "1_8": m = NMSBlockData.getMethod("b"); break; case "1_9": case "1_10": m = NMSBlockData.getMethod("s"); break; case "1_11": m = NMSBlockData.getMethod("u"); break; case "1_12": m = NMSBlockData.getMethod("t"); break; default: m = NMSBlockData.getMethod("getStateMap"); break; } } catch (SecurityException | IllegalArgumentException | NoSuchMethodException e) { e.printStackTrace(); } return m; } public Object getBlockPosition(Location loc) { try { return blockPosConstructor.newInstance(loc.getX(), loc.getY(), loc.getZ()); } catch (Exception e) { e.printStackTrace(); return null; } } public Object getNMSBlock(Location loc) { Object blockData = getBlockData(loc); if(blockData != null) { try { Object NMSBlock = NMSgetBlock.invoke(blockData); return NMSBlock; } catch (Exception e) { e.printStackTrace(); } } return null; } public Material getMaterial(Location loc) { Object blockData = getBlockData(loc); if(blockData != null) { try { Object NMSBlock = NMSgetBlock.invoke(blockData); return ((Material)CMNgetMaterial.invoke(this.NMSBlock, NMSBlock)); } catch (Exception e) { e.printStackTrace(); } } return null; } public Object[] getMaterialAndData(Location loc) { Object blockData = getBlockData(loc); if(blockData != null) { try { Object NMSBlock = NMSgetBlock.invoke(blockData); Object[] data = {((Material)CMNgetMaterial.invoke(this.NMSBlock, NMSBlock)), blockData}; return data; } catch (Exception e) { e.printStackTrace(); } } return null; } @SuppressWarnings("unchecked") public int getCropData(Object bd) { if(bd == null) return 0; try { ImmutableMap o = (ImmutableMap) BlockStateMethod.invoke(bd); Entry entry = o.entrySet().stream().filter(e -> (e.getKey().getClass() == BlockStateInteger)).findFirst().orElse(null); if(entry != null) { Object value = entry.getValue(); int data = (int) value; return data; } } catch (SecurityException | IllegalArgumentException | IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); } return 0; } @SuppressWarnings("unchecked") public int getDirection(Object bd) { if(bd == null) return 0; try { ImmutableMap o = (ImmutableMap) BlockStateMethod.invoke(bd); Entry entry = o.entrySet().stream().filter(e -> (e.getKey().getClass() == BlockStateDirection)).findFirst().orElse(null); if(entry != null) { Object value = entry.getValue(); switch(value.toString().toUpperCase()) { case "NORTH": return 2; case "SOUTH": return 0; case "EAST": return 3; case "WEST": return 1; } } } catch (SecurityException | IllegalArgumentException | IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); } return 0; } @SuppressWarnings("unchecked") public int getLumberData(Object bd) { if(bd == null) return 0; try { ImmutableMap o = (ImmutableMap) BlockStateMethod.invoke(bd); Entry entry = o.entrySet().stream().filter(e -> (e.getKey().getClass() == BlockStateEnum && e.getValue().getClass().getSimpleName().equals("EnumLogVariant"))).findFirst().orElse(null); if(entry != null) { Object value = entry.getValue(); int data = (int) value.getClass().getMethod("a").invoke(value); return data; } } catch (NoSuchMethodException | SecurityException | IllegalArgumentException | IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); } return 0; } public Object generateBlockData(Material type, int dura) { Object data = null; try { Object nmsBlock = CMNgetBlock.invoke(NMSCraftMagicNumbers, type); Method getBlockData = nmsBlock.getClass().getMethod("getBlockData"); data = getBlockData.invoke(nmsBlock); if(isLegacy) { Method fromBlockData = nmsBlock.getClass().getMethod("fromLegacyData", int.class); data = fromBlockData.invoke(nmsBlock, dura); } } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { e.printStackTrace(); } return data; } public void setBlock(Location loc, Material type, int data, boolean physics, Object customBlockData) { try { Object craftWorld = getCraftWorld(loc); Class craftworldClass = craftWorld.getClass(); Object[] combinedData = {type,data}; Object blockData = generatedBlockData.containsKey(combinedData) ? generatedBlockData.get(combinedData) : generateBlockData(type, data); if(!generatedBlockData.containsKey(combinedData)) generatedBlockData.put(combinedData, blockData); Method setTypeAndData = craftworldClass.getSuperclass().getMethod("setTypeAndData", NMSBlockPosition, NMSBlockData, int.class); setTypeAndData.invoke(craftWorld, getBlockPosition(loc), customBlockData != null ? customBlockData : blockData, physics ? 3 : 2); } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { e.printStackTrace(); } } }