Solved Grindstone Container odd slot behavior

Discussion in 'Spigot Plugin Development' started by Qruet, Feb 2, 2020.

  1. As the title suggests, I am receiving unintended behavior from using the grindstone GUI. It's difficult to pinpoint the cause of it and how to tackle the issue since I am certain the issue comes from somewhere in-between the client and server. Basically if a player spam clicks (right clicks) the item, usually in the second slot, then the item may occasionally simply disappear. This isn't however a visual bug, the server will actually lose the item which is what makes this so concerning. I have been trying several different (random) things, however I am completely lost here with no sort of guidance as to what to do next to try to avoid having this issue without creating some complicated mechanic that tracks the player's actions within the inventory and independently calculates the items the player should have once he closes the inventory to ensure no items are lost. This, however, shouldn't be necessary, so I want to know how to go about this more efficiently. All the sourcecode for the custom container (GUI) I am creating here is in the spoiler below. Also you'll find a gif under the media spoiler to visually demonstrate the issue at hand.

    Also, please note there is no stacktrace to report.

    Code (Java):
    public class SmithingContainer extends ContainerGrindstone {

        private final IInventory resultInventory;
        private final IInventory craftInventory;

        public SmithingContainer(int i, PlayerInventory playerinventory, ContainerAccess containeraccess) {
            super(i, playerinventory, containeraccess);

            [...]

            slots.set(0, new Slot(this.craftInventory, 0, 49, 19) {
                public boolean isAllowed(ItemStack itemstack) {
                    Bukkit.broadcastMessage(T.C("&7#isAllowed1(" + i(itemstack).getType() + " x" + i(itemstack).getAmount() + ")"));
                    Tasky.sync(t -> {
                        updateClient(playerinventory, true);
                    });

                    return CraftingRegistrar.RECIPE_TYPES.contains(i(itemstack).getType());
                }
            });
            slots.set(1, new Slot(this.craftInventory, 1, 49, 40) {
                public boolean isAllowed(ItemStack itemstack) {
                    Bukkit.broadcastMessage(T.C("&7#isAllowed2(" + i(itemstack).getType() + " x" + i(itemstack).getAmount() + ")"));

                    if (i(playerinventory.getCarried()).getType() == Material.AIR && i(craftInventory.getItem(1)).getType() == Material.AIR) {
                        //updateClient(playerinventory, true);
                    }
                    Tasky.sync(t -> {
                        updateClient(playerinventory, true);
                    });


                    return i(itemstack).getType() == i(craftInventory.getContents().get(0)).getType();
                }
            });
            slots.set(2, new Slot(this.resultInventory, 2, 149, 34) {
                public boolean isAllowed(ItemStack itemstack) {
                    return false;
                }

                public ItemStack a(EntityHuman entityhuman, ItemStack itemstack) {
                    containeraccess.a((world, blockposition) -> {
                        world.triggerEffect(1042, blockposition, 0);
                    });

                    ItemStack f = SmithingContainer.this.craftInventory.getItem(0);
                    ItemStack s = SmithingContainer.this.craftInventory.getItem(1);

                    ItemStack itemStack = items.get(2);

                    f.setCount(i(f).getAmount() - i(itemStack).getAmount());
                    s.setCount(i(s).getAmount() - i(itemStack).getAmount());

                    SmithingContainer.this.craftInventory.setItem(0, f);
                    SmithingContainer.this.craftInventory.setItem(1, s);

                    SmithingContainer.this.resultInventory.setItem(2, ItemStack.a);

                    return itemstack;
                }
            });

            addSlotListener(new ICrafting() {
                @Override
                public void a(Container container, NonNullList<ItemStack> nonNullList) {
                    if (container.windowId != windowId)
                        return;
                    if (i == 2)
                        return;
                    u(playerinventory, i);
                }

                @Override
                public void a(Container container, int i, ItemStack itemStack) {
                    if (container.windowId != windowId)
                        return;
                    if (i == 2)
                        return;
                    u(playerinventory, i);
                }

                @Override
                public void setContainerData(Container container, int i, int i1) {
                }
            });
        }

        @Override
        public boolean canUse(EntityHuman entityhuman) {
            return true;
        }

        @Override
        public void a(IInventory iinventory) {
            super.c();
        }

        private org.bukkit.inventory.ItemStack i(ItemStack item) {
            return CraftItemStack.asBukkitCopy(item);
        }

        private void u(PlayerInventory inventory, int i) {
            org.bukkit.inventory.ItemStack a = i(craftInventory.getItem(0));
            org.bukkit.inventory.ItemStack b = i(craftInventory.getItem(1));

            if (a.getType() == Material.AIR || b.getType() == Material.AIR) {
                this.resultInventory.setItem(0, ItemStack.a);
                return;
            }

            int min = Math.min(i(items.get(0)).getAmount(), i(items.get(1)).getAmount());
            int mL = Math.min(Int.P(NBTUtil.get(a, "reinforced")), Int.P(NBTUtil.get(b, "reinforced")));
            mL = Math.max(mL, 0);

            org.bukkit.inventory.ItemStack result = new org.bukkit.inventory.ItemStack(a.getType(), min);
            ItemMeta meta = result.getItemMeta();
            meta.setDisplayName(T.C("&fReinforced " + WordUtils.capitalize(result.getType().toString().toLowerCase().replaceAll("_", " "))));
            result.setItemMeta(meta);

            result.addUnsafeEnchantment(Enchantment.DURABILITY, mL + 1);

            result = NBTUtil.set(result, "reinforced", String.valueOf(mL + 1));

            this.resultInventory.setItem(0, CraftItemStack.asNMSCopy(result));
        }

        private void updateClient(PlayerInventory inventory, boolean cursor) {
            //debug
            Bukkit.broadcastMessage("=== CRAFT ===");
            Bukkit.broadcastMessage(T.C("&3&o" + craftInventory));
            Bukkit.broadcastMessage("=== HELD ===");
            Bukkit.broadcastMessage(T.C("&c&o" + i(inventory.getCarried()).getType() + " &7&ox" + i(inventory.getCarried()).getAmount()));
            Bukkit.broadcastMessage(" ");

            PacketPlayOutWindowItems packet = new PacketPlayOutWindowItems(windowId, items);
            ((EntityPlayer) inventory.player).playerConnection.sendPacket(packet);
            if (cursor) {
                PacketPlayOutSetSlot packet2 = new PacketPlayOutSetSlot(-1, -1, inventory.getCarried());
                ((EntityPlayer) inventory.player).playerConnection.sendPacket(packet2);
            }
        }

    [​IMG]
     
  2. Add a check to see if it disappears? And if it does disappear make it re appear
     
  3. This sounds like a suggestion to make a mechanic that tracks the inventory actions. If the item disappears, I need to know A) was it suppose to disappear (e.g. player clicked and dragged the item out of the inventory) B) what item is suppose to be there (this will involve some sort of tracking and indepedent calculations since the issue is that this isn't a visual glitch, the disappearance occurs on the serverside)

    This sort of information and calculations is already done by the Container. I just need to understand why the Container suddenly decides the item should no longer exist in the inventory. Knowing this can help me avoid this issue instead recoding something that is already well implemented into the Container's sourcecode.
     
  4. How about you add a debug message on each method to see what is called when the item disappears?
     
  5. Check the media spoiler in my first post ;)

    Im already debugging, however it hasn't yet been entirely helpful. At some random point, the item disappears. It's hard to replicate, since it involves a lot of random (spam) clicking. Somehow the client and server are out of sync, but this somehow also causes the server to lose the item. By default of course this shouldn't ever happen. Not sure what I have done that could allow for this to happen.
     
  6. Could you try and make it run asynchronously?
     
  7. I've given what you suggested a try. Running a debug asynchronously hasn't shown anything interesting. Although I believe this issue I'm having appears to only occur when I am placing in the custom enchanted item with NBT data.

    EDIT: The bug seems to occur often when attempting to "drag" items across the first two slots in the inventory.
     
    #9 Qruet, Feb 8, 2020
    Last edited: Feb 8, 2020
  8. So I found a potentially temporary solution. For some odd reason, there is some kind of miscalculation with the QUICK_CRAFT action. Cancelling seems to prevent the original issue, however, I can't say I like cancelling an inventory action so I might look into developing a custom way of handling the action, however, for the time being, from the testing I've done thus far, it appears to have little effect on the placement and movement of items in the inventory so it might be fine as is? Leaving thread as solved, but I am open for any suggestions.

    My solution below:
    Code (Java):
        @Override
        public ItemStack a(int i, int j, InventoryClickType inventoryclicktype, EntityHuman entityhuman) {
            if (i < 2) {
                Tasky.sync(t -> {
                    u(entityhuman.inventory, i); //calculate possible result
                    Tasky.sync(t1 -> {
                        updateClient(entityhuman.inventory, true); //send packets
                    });
                });
                if (inventoryclicktype == InventoryClickType.QUICK_CRAFT)
                    return ItemStack.a; //cancel event
            }
            return super.a(i, j, inventoryclicktype, entityhuman);
        }
     
    #10 Qruet, Feb 8, 2020
    Last edited: Feb 9, 2020