Util Make your nms/cb plugin version independent! [Reflections]

Discussion in 'Resources' started by FisheyLP, May 17, 2015.

?

Do you find this useful?

  1. Yes :D

    2 vote(s)
    28.6%
  2. No

    3 vote(s)
    42.9%
  3. I don't know

    2 vote(s)
    28.6%
Thread Status:
Not open for further replies.
  1. You finished your awsome plugin that involves packets and nms stuff? It works great on your server with the same version as the plugin, but on other versions you get a ClassNotFoundException... ?
    This is because each craftbukkit and minecraft server classes are in the package (for example):
    net.minecraft.server.v1_7_R2.SomeClass
    or
    org.bukkit.craftbukkit.v1_7_R2.SomeOtherClass
    You just imported the library (craftbukkit) to use it in your code, but your plugin gets the resources of the library from an outside library (the one you use to use your server). And when the server's craftbukkit is an other version, it cannot find the class you wanna use and a ClassNotFoundException will be thrown when you try to access such a class.
    I made this class to avoid these problems and make your plugin version independent.
    The class (open)
    Code:
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    
    import org.bukkit.Bukkit;
    import org.bukkit.World;
    import org.bukkit.entity.Entity;
    import org.bukkit.entity.Player;
    
    public class Reflections {
       
        public static String cbVer() {
            return "org.bukkit.craftbukkit."+ver()+".";
        }
        public static String nmsVer() {
            return "net.minecraft.server."+ver()+".";
        }
        public static String ver() {
            String pkg = Bukkit.getServer().getClass().getPackage().getName();
            return pkg.substring(pkg.lastIndexOf(".") + 1);
        }
       
        public static Class<?> wrapperToPrimitive(Class<?> clazz) {
            if(clazz == Boolean.class) return boolean.class;
            if(clazz == Integer.class) return int.class;
            if(clazz == Double.class) return double.class;
            if(clazz == Float.class) return float.class;
            if(clazz == Long.class) return long.class;
            if(clazz == Short.class) return short.class;
            if(clazz == Byte.class) return byte.class;
            if(clazz == Void.class) return void.class;
            if(clazz == Character.class) return char.class;
            return clazz;
        }
        public static Class<?>[] toParamTypes(Object... params) {
            Class<?>[] classes = new Class<?>[params.length];
            for(int i = 0; i < params.length; i++)
                classes[i] = wrapperToPrimitive(params[i].getClass());
            return classes;
        }
        public static Object getHandle(Entity e) {
            return callMethod(e, "getHandle");
        }
        public static Object getHandle(World w) {
            return callMethod(w, "getHandle");
        }
       
        public static Object playerConnection(Player p) {
            return playerConnection(getHandle(p));
        }
        public static Object playerConnection(Object handle) {
            return getDeclaredField(handle, "playerConnection");
        }
       
        public static void sendPacket(Player p, Object packet) {
            Object pc = playerConnection(p);
            try {
                pc.getClass().getMethod("sendPacket", getNMSClass("Packet")).invoke(pc, packet);
            } catch(Exception e) {
                e.printStackTrace();
            }
        }
       
        public static Object getPacket(String name, Object... params) {
            return callDeclaredConstructor(getNMSClass(name), params);
        }
    
        public static void sendJson(Player p, String json) {
            Object comp = callDeclaredMethod(getNMSClass("ChatSerializer"),
                    "a", json);
            sendPacket(p, getPacket("PacketPlayOutChat", comp, true));
        }
        public static Class<?> getClass(String name) {
            try {
                return Class.forName(name);
            } catch(Exception e) {
                return null;
            }
        }
        public static Class<?> getNMSClass(String name) {
            return getClass(nmsVer()+name);
        }
        public static Class<?> getCBClass(String name) {
            return getClass(cbVer()+name);
        }
       
        public static Object callDeclaredMethod(Object object, String method, Object... params) {
            try {
                Method m = object.getClass().getDeclaredMethod(method, toParamTypes(params));
                m.setAccessible(true);
                return m.invoke(object, params);
                } catch(Exception e) {
                    e.printStackTrace();
                    return null;
                }
        }
        public static Object callMethod(Object object, String method, Object... params) {
            try {
                Method m = object.getClass().getMethod(method, toParamTypes(params));
                m.setAccessible(true);
                return m.invoke(object, params);
                } catch(Exception e) {
                    e.printStackTrace();
                    return null;
                }
        }
        public static Object callDeclaredConstructor(Class<?> clazz, Object... params) {
            try {
                Constructor<?> con = clazz.getDeclaredConstructor(toParamTypes(params));
                con.setAccessible(true);
                return con.newInstance(params);
                } catch(Exception e) {
                    e.printStackTrace();
                    return null;
                }
        }
        public static Object callConstructor(Class<?> clazz, Object... params) {
            try {
                Constructor<?> con = clazz.getConstructor(toParamTypes(params));
                con.setAccessible(true);
                return con.newInstance(params);
                } catch(Exception e) {
                    e.printStackTrace();
                    return null;
                }
        }
    
        public static Object getDeclaredField(Object object, String field) {
            try {
            Field f = object.getClass().getDeclaredField(field);
            f.setAccessible(true);
            return f.get(object);
            } catch(Exception e) {
                e.printStackTrace();
                return null;
            }
        }
        public static Object getField(Object object, String field) {
            try {
            Field f = object.getClass().getField(field);
            f.setAccessible(true);
            return f.get(object);
            } catch(Exception e) {
                e.printStackTrace();
                return null;
            }
        }
        public static void setDeclaredField(Object object, String field, Object value) {
            try {
                Field f = object.getClass().getDeclaredField(field);
                f.setAccessible(true);
                f.set(object, value);
            } catch(Exception e) {
                e.printStackTrace();
            }
        }   
        public static void setField(Object object, String field, Object value) {
            try {
                Field f = object.getClass().getField(field);
                f.setAccessible(true);
                f.set(object, value);
            } catch(Exception e) {
                e.printStackTrace();
            }
        }   
    }

    Normally you send a packet like this:
    Code:
    PacketPlayOutOpenSignEditor packet = new PacketPlayOutOpenSignEditor(x, y, z);
    ((CraftPlayer) p).getHandle().playerConnection.sendPacket(packet);
    But the code above uses imports from nms and cb classes.
    Thats how to do it with my class:
    Code:
    Object packet = Reflections.getPacket("PacketPlayOutOpenSignEditor", x, y, z);
    Reflections.sendPacket(p, packet);
    Or even simpler:
    Code:
    Reflections.sendPacket(p, Reflections.getPacket("PacketPlayOutOpenSignEditor", x, y, z));
    It has these functions:
    • Get the version (such as v1_7_R2)
    • Get the String for nms package ("net.minecraft.server.v_1_7_R2.")
    • Get the String for cb package ("org.bukkit.craftbukkit.v_1_7_R2.")
    • Convert wrapper classes to primitive classes (for example Integer to int)
    • Convert method/constructor parameters to their type
    • Get the handle of an entity or a world
    • Get the playerConnection
    • Send a packet
    • "Build" a packet
    • Send json-messages
    • Get class from String
    • Get nms class from String
    • Get cb class from String
    • Call a method
    • Call a declared method
    • Call a constructor
    • Call a declared constructor
    • Get a field
    • Get a declared field
    • Set a field
    • Set a declared field
     
    Last edited: May 30, 2015
  2. @FisheyLP No no no no no no no no no no.... no. This is wrong. Wrong! People need to stop spreading this. This recommendation is everywhere, but it is bad. Don't ever do this. Look into the reasons why this safeguard is in place, and then look into the proper way to support multiple versions.

    I seriously judge the competence of anybody who uses this method.
     
    Thury, teej107 and timtower like this.
  3. Offline

    timtower Administrator Administrator Moderator

    @FisheyLP I also use reflection, but that isn't for NMS.
    NMS changes, so the chance could give you massive NoMethodFoundException errors. And calling huge blocks of code is really nasty.
     
    AdamQpzm likes this.
  4. Offline

    xTrollxDudex

    Not that it is totally bad... It's best kept insider information for people to find when they are at a high enough level of experience programming that they understand maintenance requirements and other factors which come into play to make the right decision on NMS.

    I still wouldn't recommend making a huge public tutorial on it.
     
  5. Offline

    RawCode

    It does not make your plugin version independent, it make your plugin harmful and dangerous for use.

    There is no such concept as "forward compatability" any plugin that implement anything like this should be rejected\removed.
     
  6. Offline

    RingOfStorms

    I agree with the two above me. The title of this thread should not be deemed a version independent solution because it is most certainty not a way to make version independent plugins. Maybe instead you should rename the title to something more along the lines of "how to use reflection in your plugins"

    The reason why you should not call this version independent is because method names and field names still change from version to version, and all you're doing with reflection is making it impossible for your IDE to inform you that those things have changed, so really doing this requires much more maintenance each update, but is good because most of the time it will result in less code change.
     
  7. Offline

    xTrollxDudex

Thread Status:
Not open for further replies.

Share This Page