NMS NMSHelper - Makes NMS code, compatible across all versions!

Discussion in 'Resources' started by Chiller, Jul 25, 2015.

?

How do you currently implement NMS?

  1. Special cases for each version of Bukkit

    23.5%
  2. A hacked up implementation of java reflection

    23.5%
  3. I just switched to NMSHelper

    17.6%
  4. Other, reply below

    0 vote(s)
    0.0%
  5. I don't

    35.3%
Thread Status:
Not open for further replies.
  1. Offline

    Chiller

    So I just created this NMSHelper class to help you with making your plugin that includes NMS code, version independent!

    No more special switch or if statements choosing this version of the class instead of this one...
    Just use this class which uses java reflection to automatically detect the servers current bukkit version and select the correct classes and packages based on that!

    Github Gist

    Now includes the ability to specify the method name and argument/argument types for specific versions!

    How to use:

    1. Import your NMS classes that are needed
    Code:
    // import net.minecraft.server._version_.ItemStack;
    // import net.minecraft.server._version_.NBTTagCompound;
    // import org.bukkit.craftbukkit._version_.inventory.CraftItemStack;
    // Translates to:
    
    NMSHelper.importClass("net.minecraft.server._version_.ItemStack");
    Class<?> NBTTagCompound = NMSHelper.importClass("net.minecraft.server._version_.NBTTagCompound");
    Class<?> CraftItemStack = NMSHelper.importClass("org.bukkit.craftbukkit._version_.inventory.CraftItemStack");
    Keep that _version_ in there, you need that so that the server can decide which package the class is in!

    2. Create a new instance of a class
    Code:
    // new CraftItemStack("wood", 1);
    // Translates to:
    
    Object nmsStack = NMSHelper.newInstance(CraftItemStack, "wood", 1);
    3. Call regular methods on objects
    Code:
    // v1.7: boolean hasTag = nmsStack.hasMyTag();
    // v1.8: boolean hasTag = nmsStack.hasTag();
    // Translates to:
    
    boolean hasTag = (Boolean) NMSHelper.buildMethod(nmsStack)
            .addVersionMethod("1.7", "hasMyTag")
            .addVersionMethod("1.8", "hasTag")
            .execute();
    
    // Universal: boolean hasTag = nmsStack.hasTag();
    // Translates to:
    
    boolean hasTag = (Boolean) NMSHelper.buildMethod(nmsStack)
            .addUniversalMethod("hasTag")
            .execute();
    
    // v1.7: nmsStack.setMyTag(tag, "tagName");
    // v1.8: nmsStack.setTag(tag);
    // Translates to:
    
    NMSHelper.buildMethod(nmsStack)
        .addVersionMethod("1.7", "setMyTag")
        .addVersionMethod("1.8", "setTag")
        .addArguments("1.7", tag, "tagName")
        .addArguments("1.8", tag)
        .execute();
    
    // Universal: nmsStack.setTag(tag);
    // Translates to:
    
    NMSHelper.buildMethod(nmsStack)
        .addUniversalMethod("setTag")
        .execute(tag);
    4. Call static methods on classes
    Code:
    // v1.7: CraftItemStack nmsStack = CraftItemStack.asNMSCopy(item);
    // v1.8: CraftItemStack nmsStack = CraftItemStack.nmsCopy(item);
    // Translates to:
    Object nmsStack = NMSHelper.buildStaticMethod(CraftItemStack)
            .addVersionMethod("1.7", "asNMSCopy")
            .addVersionMethod("1.8", "nmsCopy")
            .addArguments("1.7", item)
            .addArguments("1.8", item)
            .execute();
    
    // Universal: CraftItemStack nmsStack = CraftItemStack.asNMSCopy(item);
    // Translates to:
    Object nmsStack = NMSHelper.buildStaticMethod(CraftItemStack)
            .addUniversalMethod("nmsCopy")
            .execute(item);
    5. Get class object
    Code:
    // class CraftItemStack
    // Translates to:
    
    Class<?> CraftItemStack = NMSHelper.getClass("org.bukkit.craftbukkit._version_.inventory.CraftItemStack");
    // or
    Class<?> CraftItemStack = NMSHelper.getClass("CraftItemStack");

    I created this so that I could add an enchantment glow to an item without adding the lore to the item!
    Here is the code I used for that:
    Item glow (open)

    Code:
    public static ItemStack addGlow(ItemStack item)
    {
        try
        {
            NMSHelper.importClass("net.minecraft.server._version_.ItemStack");
          
            Class<?> CraftItemStack = NMSHelper.importClass("org.bukkit.craftbukkit._version_.inventory.CraftItemStack");
            Class<?> NBTTagCompound = NMSHelper.importClass("net.minecraft.server._version_.NBTTagCompound");
            Class<?> NBTTagList = NMSHelper.importClass("net.minecraft.server._version_.NBTTagList");
            Class<?> NBTBase = NMSHelper.importClass("net.minecraft.server._version_.NBTBase");
          
            Object nmsStack = NMSHelper.buildStaticMethod(CraftItemStack).addUniversalMethod("asNMSCopy").execute(item);
            Object tag = null;
          
            if (!((Boolean) NMSHelper.buildMethod(nmsStack).addUniversalMethod("hasTag").execute()))
            {
                tag = NMSHelper.newInstance(NBTTagCompound);
              
                NMSHelper.buildMethod(nmsStack).addUniversalMethod("setTag").execute(tag);
            } else
            {
                tag = NMSHelper.buildMethod(nmsStack).addUniversalMethod("getTag").execute();
            }
          
            Object ench = NMSHelper.newInstance(NBTTagList);
          
            NMSHelper.buildMethod(tag).addUniversalMethod("set", String.class, NBTBase).execute("ench", ench);
            NMSHelper.buildMethod(nmsStack).addUniversalMethod("setTag").execute(tag);
          
            ItemStack stack = (ItemStack) NMSHelper.buildStaticMethod(CraftItemStack).addUniversalMethod("asCraftMirror").execute(nmsStack);
          
            return stack;
        } catch (IllegalAccessException e)
        {
            e.printStackTrace();
        } catch (IllegalArgumentException e)
        {
            e.printStackTrace();
        } catch (InvocationTargetException e)
        {
            e.printStackTrace();
        } catch (NoSuchMethodException e)
        {
            e.printStackTrace();
        } catch (SecurityException e)
        {
            e.printStackTrace();
        } catch (ClassNotFoundException e)
        {
            e.printStackTrace();
        } catch (Exception e)
        {
            e.printStackTrace();
        }
      
        return null;
    }


    Please notify me of any bugs or errors that you get from using it!
    Also reply if I didn't explain something very well!
     
    Last edited: Jul 29, 2015
  2. Offline

    567legodude

    @Chiller I don't think this is the right section for this. This should be in the Resources section.
     
  3. Offline

    Chiller

    @567legodude More developers will see it here, if they are about to post a thread asking for NMS help then they will see this and see this!
     
  4. Offline

    567legodude

    @Chiller I think one of the mods will move it to resources anyway, because that's where it should be.

    That's what it's for, people go to Resources when they need....... Resources....
     
  5. Offline

    Chiller

    @567legodude Probably, and mod, when you do can you delete these 4 reply's
     
  6. Offline

    567legodude

    @Chiller I think this will be helpful to many people, I've never needed to use NMS before, so I don't really know how to use it.
     
  7. Offline

    Konato_K

    @Chiller I didn't check your code very much, but for what I saw, it will still break if the package/field name is different between versions.
     
  8. Offline

    Chiller

    @Konato_K Yes you are correct about that, but through minor releases they shouldn't change...
     
  9. Offline

    Konato_K

    @Chiller Yes, they normally not change in minor releases, but then is not compatible "across all versions"
     
    Shortninja66 and xTrollxDudex like this.
  10. Offline

    mythbusterma

    @Chiller

    Just wait until @1Rogue sees this....


    The entire point of changing the names of NMS classes every version is to break plugins that rely on the underlying source. Before this happened, plugins broke all the time because of changes that occurred unbeknownst to ignorant (or unaware) developers. Deliberate attempts to circumvent this system not only confuse, but astound me in their ignorance of what they are actually doing.

    Honestly, this thread should be removed.
     
  11. Offline

    567legodude

    @mythbusterma I'm a little curious, what does this one do that makes it so bad?
     
  12. Offline

    mrCookieSlime

    Moved to Resources.
     
  13. Offline

    mythbusterma

    @567legodude

    Let's say EntityPlayer has a method "a()" that takes one argument, a String. In the next release, a method is changed in the EntityPlayer class, thereby reassigning all the obfuscated method names. In the best case, "a()" now takes no arguments, and now causes your code to fail with a nearly indecipherable stack trace (to the average Bukkit user/FBO poster) and at worst, it's still a method that takes one String, but now the method directly sends that information to the client and crashes the client because it doesn't recognize the data. Now you have clients randomly crashing off your server. Good luck diagnosing that bug.

    Obviously that's an extreme example, but not out of the realm of possibility.
     
  14. Offline

    1Rogue

    On the other end, this is a lot more work than simply changing a couple methods on updating. If you're going for a system that changes dynamically for bukkit versions, look into mapping with maven.
     
  15. Offline

    Chiller

  16. Offline

    1Rogue

    My point still stands.
     
  17. Cleaned up thread, please try to keep your messages constructive and nice. Constructive criticism is fine as long as it is not said the wrong way.
     
  18. Offline

    567legodude

    @mythbusterma Well, maybe someday someone will make a version of this that can detect method changes, but until then.....
     
  19. Offline

    mythbusterma

    @567legodude

    But until then we can actually do it properly, and while we're at it, why don't we do it properly forever?

    It's SUPER easy to detect method changes! Just use this one simple step:

    import net.minecraft.server.<insert version number here>.<package name>.<class name>

    Then use the class you imported! It really is just that easy.

    For information on how to actually do this properly and create one version independent .jar, see this repository.
     
  20. Offline

    MCMatters

    @mythbusterma you obiously hate reflection don't you?

    @Chiller github it

    <Edited by bwfcwalshy: Merged posts, please use the edit button rather than double posting.>
     
    Last edited by a moderator: Jul 28, 2015
  21. Offline

    teej107

    It has its uses but it's best not to be used as a shortcut.
     
  22. Offline

    Chiller

  23. Offline

    RawCode

    again and again i see such "methods" to "overcome" version barrier and again suggestion completely invalid and harmful.
    you really should check "large" plugins like WorldEdit or just use search over existing threads. (TAG API is fine).

    valid method of supporting multiple versions is building handler class vs specific version and using that handler class via interface.
    if version lookup failed or handler class failed to bind, issue easy to detect and nothing harmful will happen.

    this atleast force developer to manually check plugin vs each supported version instead of placing "WORKS ON EVERY VERSION" stupidity.

    more advanced version of this will be custom JIT compiler that will assemble code at runtime from prototype and payload or plain text, this a bit more complex to implement but allows to add supported versions without changing JAR, by dropping payload\plaintext into plugin folder.
     
    Europia79 likes this.
  24. Offline

    mythbusterma

    @MCMatters

    Reflection is an extremely important part of the language and is incredibly useful. However, using it to be lazy and degrade the quality of software makes the creators of the language cry a little.
     
  25. Offline

    MCMatters

  26. Offline

    teej107

    and?
     
  27. Offline

    Konato_K

    Europia79 likes this.
  28. Offline

    mythbusterma

    @MCMatters

    And as Teej and Konato have said, that is exactly the problem.
     
  29. Offline

    guitargun

    Side question here.. How dynamic is this with.maven? Never used it before and I have a few reflection methods lying around and methods without reflection.. And with the latest update of my server I see your point in the crashes of servers and clients
     
  30. Offline

    xTrollxDudex

    Konato_K likes this.
Thread Status:
Not open for further replies.

Share This Page