Updating an entity

Discussion in 'Plugin Development' started by mine-care, Jun 20, 2016.

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

    mine-care

    Hello,
    I was trying to make a villager change colors randomly, by changing it's proffesion every X ammount of time. It all goes well, but the villager on the client-side is not changing profession.
    I thought it was bizare and with all the debuging i did, everything goes to plan. Value 16 is set in the datawatcher each time i change the proffesion. Then i thought that the reason it isn't updating is because the server isn't sending a PacketPlayOutEntityMetadata so i created one manually each time i changed the profesion and sent it to all players. Yet again this had no effect...

    Trying to change the CustomDisplayName of the villager to something different than what it was when it first spawned, isnt working either, because although the values change on the server side, and a PacketPlayOutEntityMetadata is sent to the client with the id of the entity and it's datawatcher, the client seems to take no action on updating this entity. I also tried to toggle the visibility of the name every time i changed it just in case the client handles that but aparently it doesn't... There are no errors in the code during compilation, nor during runtime.

    Here is the code used to spawn the villager:
    Code:
    private Random random = new Random();
    private CustomVillager shop;
    
    
    public void spawnShop(Location l) {
        if(shop != null) throw new IllegalStateException("Shop already spawned");
        shop = new CustomVillager(l.getWorld(), 1, l);
        shop.setCustomName(ChatColor.GREEN + "Shop");
        shop.setCustomNameVisible(true);
        new BukkitRunnable() {
            @Override
            public void run() {
                shop.setCustomName(ChatUtilities.randomizeString("Shop"));
                shop.setProfession(Villager.Profession.values()[random.nextInt(Villager.Profession.values().length)].getId());
                shop.update(); //Custom method used to attempt to update the villager...
            }
        }.runTaskTimer(instance, 0L, 200L);
    }
    
    And the method update() in the custom villager class:

    Code:
    public void update() {
            for (Player p : Bukkit.getOnlinePlayers()) {
                ((CraftPlayer) p).getHandle().playerConnection
                        .sendPacket(new PacketPlayOutEntityMetadata(getId(), datawatcher, false));
            }
    }
    
    Am i missing something here :?

    Thanks!
     
  2. Offline

    I Al Istannen

    @mine-care
    What about this?
    Code:
                    new BukkitRunnable() {
                    
                        int counter = 0;
                        @Override
                        public void run() {
                            if(villager == null || !villager.isValid()) {
                                this.cancel();
                                return;
                            }
                            villager.setProfession(Profession.values()[counter % Profession.values().length]);
                            counter++;
                        }
                    }.runTaskTimer(ToyAround.getInstance(), 0, 20);
    I just don't know if the setProfession method is in your Bukkit version.

    The craftbukkit code for updating is here.

    Which points to this NMS code:
    Code:
    public void setProfession(final int i) {
      this.datawatcher.watch(16, i);
    }
    
    If you need more, you may need to dig a bit in the NMS code.

    Easiest would be if you have a version with the setProfession code. (Which is in 1.7.10 and later, maybe even before that)

    EDIT: Justs saw you also used this method. Well, it works for me...

    EDIT 2: Proof:
    changing villager.gif
     
    mine-care likes this.
  3. Offline

    mine-care

    @I Al Istannen I am using 1.8.3 as my server version (1.8.3 is the version of nms classes at least.) and my client is 1.8.9.
    Its encouraging to see it work really, because for a second i thought that i can only manipulate the properties of a villager only when it spawns, and than from then on the metadata packet carries info about the health only but im glad to see that i was wrong. Now, lets see what makes the difference :/

    What it loosk for me running the code posted above:
    [​IMG]

    As you see the debug message i added prints out different names.
    Tried the same with profesion (datawatcher id 16) and it shows different numbers between 1 and 4 which is just what i want but yet the entity doesnt update as you see!!! Arr!!

    And yeah the first setCustomName value i what is kept from then on.
     
  4. Offline

    I Al Istannen

    @mine-care
    Hmm. I am running
    "This server is running CraftBukkit version git-Spigot-db6de12-1 8fbb24 (MC: 1.8.8) (Implementing API version 1.8.8-R0.1-SNAPSHOT)"
    and using a 1.8.9 client.

    Which is extremly odd. Sadly I have nearly no experience with the client, I will quickly look what packets my server sends and then edit this post. I would guess in around 5 to 10 minutes.
     
  5. Offline

    mine-care

    @I Al Istannen thanks for your effort! :)
    I concluded that packets aint sent to my client so i send them manually through the update method but that isn't the case. Ill do the same eventually.
     
  6. Offline

    I Al Istannen

    @mine-care
    Well, ProtocolLib which I used is just spitting out errors when I try to listen to that packet... . Will try some more, but can't promise anything.
    Could you get your hands on an 1.8.8 /1.8.9 server?

    EDIT: Does this work for you?:
    Code:
    new BukkitRunnable() {
      
       int counter = 0;
       @Override
       public void run() {
         if(villager == null || !villager.isValid()) {
           this.cancel();
           return;
         }
        
         Profession prof = Profession.values()[counter % Profession.values().length];
         ((CraftVillager) villager).getHandle().getDataWatcher().watch(16, prof.ordinal());
         // sending the packet on your own is redundant. The watch method should do it for you. Just to be safe, I will do it anyways :P
         PacketPlayOutEntityMetadata pack = new PacketPlayOutEntityMetadata(villager.getEntityId(), ((CraftVillager) villager).getHandle().getDataWatcher(), true);
         for(Player p : Bukkit.getOnlinePlayers()) {
           ((CraftPlayer) p).getHandle().playerConnection.sendPacket(pack);
         }
         //villager.setProfession(prof);
         counter++;
       }
    }.runTaskTimer(ToyAround.getInstance(), 0, 40);
    
     
    Last edited: Jun 20, 2016
  7. Offline

    mine-care

    I am simply overwriting PlayerConnection and more prerticularly the method sendPacket ;) (I have no reason to be version independed on that project of mine so why use protocol lib. Also i need to overwrite it to prevent a modified client feature.)
    Anyways, the packet is sent normally when the name changes but my client does not handle it aparently!!!
    [​IMG]
    I am not using any modified client. The default minecraft client version 1.8.9
    what could possibly be the cause? This is seriously strange now!

    Also at a later test to make sure the entity id was correct:
    [​IMG]
    Here you see it print "Entity id: 10" which is printed before setting the name, then it prints the new name, and then it prints the id carried by the PacketPlayOutEntityMetadata before it is sent on its way to the player. In other words, it all goes fine up to the point it leaves the server, but yet the client refuses to show any change....

    If you have any ideas please let me know :) Thanks for the effort once more :)
     
  8. Offline

    I Al Istannen

    @mine-care
    Have your tried the sending code in my last post?
    It gives me this output with the following code:
    Code:
    PacketPlayOutEntityMetadata meta = (PacketPlayOutEntityMetadata) event.getPacket().getHandle();
    if(!getField(meta, "a").equals(villager.getEntityId())) {
       return;
    }
    else {
       System.out.println("EntityId: " + getField(meta, "a") + " orig: " + villager.getEntityId());
    }
    List<WatchableObject> list = getField(meta);
    for(WatchableObject data : list) {
       if(getField(data, "b").equals(Integer.valueOf(16))) {
         System.out.println("A: " + getField(data, "a"));
         System.out.println("ID: " + getField(data, "b"));
         System.out.println("Profession value: " + getField(data, "c"));
         System.out.println("Field d: " + getField(data, "d"));
       }
    }
    The names of the field may vary on your version.
    Code:
    EntityId: 294 orig: 294
    A: 2
    ID: 16
    Profession value: 1
    Field d: false
    
    EntityId: 294 orig: 294
    A: 2
    ID: 16
    Profession value: 2
    Field d: false
    Just hoping to find some kind of error on the server, as I have absolutly NO idea of the client.
     
  9. Offline

    mine-care

    @I Al Istannen Ah!
    I did the printing of values and everything seems to be working just fine :?

    [​IMG]
    Eh :(
    Tried your code to but it made no difference... The values add up! It doesnt make a visible change though neither on the profesion nor on their name...
    Me neather! :S Never messed with it.
     
  10. Offline

    I Al Istannen

    @mine-care
    Then I am really sorry, but I probably can't help you.
    Have you tried a different client version or not setting the name?
     
  11. Offline

    mine-care

    @I Al Istannen Yep i tried not setting the name, no progress there... no change.
    Then i tried using 1.8 instead of 1.8.9 as my client, and yet again no change...... -_-

    What could possibly be it!!! Is there any setting in mc i am not aware of that enables/disables entity update????? Ahh!
     
  12. Offline

    I Al Istannen

    @mine-care
    Tried it on a different server (or different server version)?
    I had a quick glance at the client source, obviously it is too big to understand it in this timeframe, but it seems to directly get the texture from the datawatcher. And also writes it to it, if you call setProfession.

    And from the packets:
    // the func returns the list with the WatchableObjects
    "if (entity != null && packetIn.func_149376_c() != null)"

    // then later
    "if (datawatcher$watchableobject1 != null)"

    // and it writes it
    datawatcher$watchableobject1.setObject(datawatcher$watchableobject.getObject());

    There don't seem to be any other measures between calling these methods, but there might be some better hidden ones, I am not aware of.

    There may also be some other things on the rendering part.
     
  13. Offline

    mine-care

    @I Al Istannen alright ill try to update it right now! it will take me some time really because this plugin contains a lot of nms stuff that i need to update :S
     
  14. Offline

    timtower Administrator Administrator Moderator

    @mine-care Abstraction, then you don't need to transfer it every time.
     
    mine-care likes this.
  15. Offline

    mine-care

    @timtower Indeed, i dont really care about future updates because this is a plugin i maintain myself and will not be published anywhere so eh i can spend 20-30 minutes every time i change the server version ;)
     
    timtower likes this.
  16. Offline

    I Al Istannen

    @mine-care
    Sorry I wasn't able to help you any further :/

    EDIT: Just a quick thought, does your onSending mess with the packet in any way? What if you DON'T overwrite the playerConnection?
     
    Last edited: Jun 21, 2016
    mine-care and MisterErwin like this.
  17. Offline

    mine-care

    @I Al Istannen I followed your advise and i didn't overwrite it at all but yet again the same problem! Arr!!!
    **Head Explodes**
    Truth is that i manipulate outgoing PacketPlayOutEntityMetadata packets in general but in this case even without touching the outgoing packets the problem remains... ARRR!!!

    Ill try to find a solution and once i do ill be back to post it.
    Anyways, thank you for all the help! :)
     
  18. Offline

    I Al Istannen

    @mine-care
    No problem :)

    Also I am quite baffled (flabbergasted. Just needed to use this word :p). I have absolutly no further idea, considering that it works flawlessly for me... .

    I noted it above, but I am using Spigot 1.8.8 and Minecraft 1.8.9 - vanilla.

    Good luck with it!!
    And report back :)
     
  19. Offline

    mine-care

    **Browses 'translate.google.com' and types in the word flabbergasted to find a word on his language that he doesnt understand either** :D


    Updating to 1.9.X to see fo any effect :? I am feeling crazy or blind! Code is not expected to behave differently on different hands!!!!
     
  20. Offline

    I Al Istannen

    @mine-care
    I will update it quickly to 1.9.2. Let's see how that works out.. :p

    EDIT 2: Yes, I just read flabbergasted somewhere and fall in love with it :p Sounds just like the meaning it has. It is along surprised, shocked, thunderstruck.

    EDIT: Okay, some small changes during the versions. You can change the profession using packets with this:

    Code:
    Profession prof = Profession.values()[counter % Profession.values().length];
    DataWatcherObject<Integer> dataObj = new DataWatcherObject<>(12, DataWatcherRegistry.b);
    ((CraftVillager) villager).getHandle().getDataWatcher().set(dataObj, prof.ordinal());
    But the normal setProfession method works for me too. Like a charm. Try to setup a new, empty server and only make this ONE method in a runnable in the plugin. Just do nothing with the plugin except changing the type of villagers randomly.

    The packet itself is this:
    Code:
    PacketPlayOutEntityMetadata meta = (PacketPlayOutEntityMetadata) event.getPacket().getHandle();
    if(!getField(meta, "a").equals(villager.getEntityId())) {
       return;
    }
    else {
       System.out.println("EntityId: " + getField(meta, "a") + " orig: " + villager.getEntityId());
    }
    List<Item<?>> list = (List<Item<?>>) getField(meta, "b");
    for(Item<?> it : list) {
       DataWatcherObject<?> dataObj = (DataWatcherObject<?>) getField(it, "a");
       if(dataObj.a() != 12) {
         return;
       }
       System.out.println("Profession: " + getField(it, "b"));
       System.out.println("Packet ID: " + getField(dataObj, "a"));
    }
    
    and the output:
    Code:
    [17:34:23 INFO]: Profession: 1
    [17:34:23 INFO]: Packet ID: 12
    [17:34:25 INFO]: EntityId: 533 orig: 533
    [17:34:25 INFO]: Profession: 2
    [17:34:25 INFO]: Packet ID: 12
     
    Last edited: Jun 21, 2016
    mine-care likes this.
  21. Offline

    mine-care

    @I Al Istannen @timtower
    UPDATE:
    After the debuging ideas provided by @I Al Istannen i came to the following anoying conclusion:
    If the villager spawns onEnable as it does currently, and there is no player on, it doesnt work. When i made it spawn through a command ingame, it worked fine. In other words if the villager spawns without players on, it doesnt animate at all. On the other hand, if there are players on, the name and profesion changes. I tried disconnecting and reconnecting and although i could see the villager animate before disconnecting, when i joined back, it had stoped but the task was still running. Its worth noting that the PacketPlayOutEntityMetadata is sent to my client with the correct details on it, but my client seems to neglect it.

    So for some reason if there are no players, or if someone joins after the task has begun, then the villager isnt animating!!! Could it be that the client gets a different ID for that entity? The PacketPlayOutSpawnEntityLiving that is sent when the player joins, is fine, it has the same correct entity id.

    All i can think of now is taking a completely packet-oriented aproach, in other words handle the spawning, animation, clicking and everything manually through packets for each player but that would be a lot of work...

    Any ideas why that is happening???
     
  22. Offline

    I Al Istannen

    @mine-care
    I can still not reproduce it :/
    You could try using the Minecraft Coder pack to compile and run the client yourself through your IDE and add some debug outputs in the packet receive methods to see what your client does with it.

    The entity id should be the same for every player.

    Is the villager from the onEnable spawned in unloaded chunks? Or in the spawn or aritficially loaded ones?

    Apart from that I have no idea, sorry :/
     
  23. Offline

    mine-care

    I dumped it for now because it has been taking me all day for the past 2-3 days to figure out whats wrong... I will continue it eventually and ill do that ;)


    it should but it may not be, I purposively caused an exception on my client to check debug console, and it worked.
    The problem is that the entity on the server had ID 9 and on my client it had ID 0 although the PacketPlayOutSpawnEntityLiving had field 'a' (ID) set to 9. The debug console showed the exception and stated:
    "Skipping Entity with id 0" when it was suposed to say 9.
    Anyways ill research further on that!

    Artifically no, and since it is spawned in a delayed task (i forgot to mention that) it is always spawned after the loading of the world and required chunk(s). I had to do it this way since the same plugin also handles bans and has to be enabled on STARTUP for obvious reasons, so a task is nessesary because locations cant be created at that point in time because worlds are not loaded.

    Dont worry! You have helped a ton already! :eek: I would have been stuck to my initial test (mentioned in the first post) if i didnt have the debuging ideas you provided ;)
     
Thread Status:
Not open for further replies.

Share This Page