Getting Entity NBT data

Discussion in 'Plugin Development' started by kyran_, Nov 25, 2012.

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

    kyran_

    How would I go about getting the NBT tags from an entity? I know they're contained within compounds in chunk > level > entities but I'm not really sure how to go about reading the NBT data. I believe it's a bit more complicated than reading TileEntity tags and requires input and output streams? Could someone post or direct me to a code snippet that shows how to read and write nbt data from the chunk files? Any other helpful information regarding processing NBT data would be appreciated as well.

    Many thanks!

    Hmm I did some more digging in the craftbukkit files and found the following class which I managed to overlook previously: https://github.com/Bukkit/CraftBukk...ava/net/minecraft/server/WorldNBTStorage.java

    This looks like it handles the streaming between .dat files, so this gives me something to fiddle with. Any further guidance would still be welcome but I wanted to post my findings here in the meantime in case other people were wondering the same.

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: May 30, 2016
  2. You mean you want to get an NBTTagCompound out of a reference of a net.minecraft.server.EntitySomething you have? (or bukkit reference, whatever)

    You can just use the "b(NBTTagCompound)" method the same way you used it in that other thread I happened to read. Or do you mean something different?

    Code:
    NBTTagCompound compound = new NBTTagCompound();
    yourEntity.b(compound);
     
  3. Offline

    kyran_

    Yeah I thought that was gonna work but it's saying b(NBTTagCompound) is undefined for entity. My other code works perfect for all tile entities but fails for mobs. Maybe there's some hidden craftbukkit feature that won't show up in the bukkit javadocs? Either way the generic method using reflection doesn't find a b method either. I think I have to go through the NBTWorldStorage class I linked above so I'm gonna tinker around today and see if I can crack it.



    Edit: Oh wow I tried calling b(NBTCompound) from LivingEntity and of course it's there[sheep]
    So now I just have to cast entities and call my generic method... Seems suspiciously easy.
    WHY CAN I NOT HAS DEOBFUSCATED CRAFTBUKKITZES? Would make this kind of thing muuuch easier if method and field names were sorted out.

    Now I can't get the method I outlined earlier to work. I'm casting the mob (in this case a pig) to LivingEntity and passing it into the generic method. But I think when the reflection tests the pig, it's dealing with the CraftPig class and not LivingEntity. It doesn't find the b method inherited from the superclass and returns without doing anything.
    Is this just because I'm casting the pig to an abstract interface? (Since the pig can't technically be an instance of an abstract interface?) Maybe I'll just have to modify the getTag method to check the superclasses if it doesn't find b().

    Here's the generic reflection method:
    Code:
        protected <T extends Object> NBTTagCompound getTag(T object, Player player){
            NBTTagCompound compound = new NBTTagCompound();
           
            Class<? extends Object> clazz = object.getClass();
            Method[] methods = clazz.getDeclaredMethods();
            for (Method method : methods){
                if ((method.getName() == "b") && (method.getParameterTypes().length == 1) && (method.getParameterTypes()[0] == NBTTagCompound.class)){
                    try {
                        method.invoke(object, compound);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
            return compound;
        }
    
    I'm thinking maybe it would be more efficient to take a polymorphic approach and just let the inheritance structure do its job... I could still have a generic method but not use reflection. Just parametrize it as <T extends Object> and run the .b(compound) in a try block. It would be easier if TileEntities were actually subclasses of Entity as the name suggests. I could always have 2 separate methods for types <T extends Entity> and <T extends TileEntity>.

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: May 30, 2016
  4. I think your problem is that "getDeclaredMethods()" only returns the methods that are actually declared in the class you call it on (excluding the ones that are inherited). EntityPig might not override the method you are lookin for, thus it's not declared in that class.

    Since you obtain "clazz" through "object.getClass()", it will always be the actual class of the parameter - independent from what you casted it to, because that's irrelevant.

    There are multiple ways to solve that if I remember correctly. For example, you could aquire the parent classes in a loop and try to locate the method there. Or you could instead use "getMethods()" - which includes all inherited and declared methods but only contains public ones or something like that (as opposed to "getDeclaredMethods()").

    And I don't really see the point of the generic type argument there. You are not actually using it. Generics are (almost?) exclusively compile-time, so it has no real effect on runtime, just usually helps you prevent casting.

    You could as well just make your parameter of type Object. The "T" is not real (you could also not do "T.class" for example).


    And TileEntities have logically - despite their name - very few in common with regular Entities.
     
  5. Offline

    kyran_

    Exactly what I suspected.

    Thanks for the tip on the getMethods, I'll check it out.

    The goal with the generics and reflection was to make it super easy to update if craftbukkit changes their methods. I'd store a method name in a variable and pass it in as a parameter for the reflection block to invoke. Users could update the variable themselves with a command and not even have to redownload an updated version of the plugin.

    You're right regarding the generic typing. All it's doing at the moment is ensuring it's a subclass of Object which is barely more specific. The point again was future extensibility, although perhaps it's not required...

    I've realized I've developed a very convoluted way of doing try{ object.b() } heh. Don't it look fancy though! And it works beautifully for TileEntities.

    Yeah apart from the fact that a want to nab their NBT data! One of the original reasons for putting in the generics was to tighten up the typing a little bit, but then I realized TileEntities and Entities are subclasses of Object.

    I also might need to clone objects I pass in, which is where the generics would come in handy. It'd probably be handled somewhere else though but, you know... consistency.

    I'll see if I can clean it up while still maintaining flexibility for things I add in the future.

    Sweet lerd jeysus I finally got it to work! (That's actually the confirmation message I printed on success heh feelsgoodman.jpg)
    I wasn't aware of the whole "cast bukkit.entity to craftEntity then .getHandle() to get the notch entity" thing... Had me combing through the console reading all the methods in craftEntity and all its superclasses wondering why I couldn't find a b method.

    Now that that's smoothed out, I've got a getTag method that takes entities and tileEntities alike and saves me from having to do a bunch of instance checks and casting:
    Code:
        protected <T extends Object> NBTTagCompound getTag(T object, Player player){
            NBTTagCompound compound = new NBTTagCompound();
            
            Class<? extends Object> clazz = object.getClass();
            Method[] methods = clazz.getMethods();
            for (Method method : methods){
                if ((method.getName() == "b") && (method.getParameterTypes().length == 1) && (method.getParameterTypes()[0] == NBTTagCompound.class)){
                    try {
                        method.invoke(object, compound);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
            return compound;
        }
    
    Like I said above, this should save a lot of hassle should the server methods change, and allow passing in a method name to search for as an argument. I'm thinking of keeping the generics in place in case I create a generic tag editor object to house the nbt code and I need to clone entities or tileEntities.

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: May 30, 2016
  6. Offline

    kyran_

  7. Good job, looks pretty solid :D
    Can (or will you be able to) you actually modify those tags through commands or a conversation? Because that would be a pretty cool development tool.
     
  8. Offline

    kyran_

    Yep that's the plan! I was gonna have it be gui based but I think for flexibility's sake I'm gonna just try to make it be really slick from within the chat using commands. The nbt edit mode is a toggle command so once you're in edit mode all your commands will be routed to the plugin. You just right click an entity or tileEntity to load its NBT files. There will be add, remove, edit commands, and then a save command to commit the compound tag back to its entity or tile.

    Any suggestions or requests are welcome, I'm designing this around my own requirements but I want it to be super flexible.
     
    Bone008 likes this.
  9. Offline

    ceoepts

    HOLY TURTLE P***S Thats some good stuff you got there... Is it up to date?
     
  10. Offline

    fireblast709

    http://github.com/Bukkit/CraftBukkit
    Not deobfuscated, but will get you further (if you didn't already know about this)
     
  11. Offline

    kyran_

    It sure is!

    Thanks, I have looked there before. They are still obfuscated though, as you mention. Also certain NBT classes aren't included for some reason. It was confusing looking around at different sources and seeing certain classes not included.

    I've long since gotten past this problem though, I had to root around in the net.minecraft.server NBT classes and mess about with reflection to get things running smoothly (had to write a wrapper for offline players). She works nicely now though, just updated to 1.1 to work with the newest CB version as well as versions back to 1.4.4. Refactoring to work with multiple craftbukkit versions was... shall we say, not exceedingly enjoyable.

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: May 30, 2016
  12. The CraftBukkit repo only contains the net.minecraft.server classes that were modified by CraftBukkit. A list of all used (unmodified) classes can be found at the mc-dev repo. It takes forever until it's updated, though and goes private every now and then.
    Many classes where it would be nice to see the inner workings aren't in the main repo (NBT, most AI pathfinders, various Block classes, etc.), so I've started just downloading the minecraft-server.jar that they compile CB against and manually decompile it. Produces code that is even more broken than mc-dev, but it's enough to look at.

    I assume that's totally unrelated to the beautiful safe-guard change, because NBT is so volatile that it completely changes every minecraft version, right? Obviously, making it backwards-compatible just got extremely more simple ...
     
  13. Offline

    fireblast709

    Bone008 that is called the Minecraft Coder Pack, which kindly renames at least a part of the methods to readable names. What is left is for you to link those renamed methods to the obfuscated names
     
  14. Offline

    kyran_

    Heh oh my... The best part is how you can't build dynamic dependencies on the fly or they'll refuse approval of your code. You can't put a price on safety, you know? I'm just really glad all the server admins with 2 digit IQs won't have to ever read anything again.
     
  15. Offline

    tommycake50

    there are server admins with 2 digit IQ's? :eek:
     
  16. Offline

    kyran_

    Apparently. Or so I would assume given the circumstances. In the same way I'd assume there were toddlers in a house with plastic covers over the wall sockets and padding on sharp corners.
     
Thread Status:
Not open for further replies.

Share This Page