NPE MerchantRecipeList spigot v1.12.2

Discussion in 'Spigot Plugin Development' started by Xx_Will33_xX, Mar 17, 2019 at 2:03 PM.

  1. Hello,
    I am having a problem opening an exchange menu. An NPE that I do not understand despite my research

    Code (Text):
    [14:54:36] [Server thread/ERROR]: Could not pass event PlayerInteractEntityEvent to ElenoxCore v3.6.1
    org.bukkit.event.EventException: null
        at org.bukkit.plugin.java.JavaPluginLoader$1.execute(JavaPluginLoader.java:306) ~[ElenoxServer.jar:git-Spigot-79a30d7-acbc348]
        at org.bukkit.plugin.RegisteredListener.callEvent(RegisteredListener.java:62) ~[ElenoxServer.jar:git-Spigot-79a30d7-acbc348]
        at org.bukkit.plugin.SimplePluginManager.fireEvent(SimplePluginManager.java:500) [ElenoxServer.jar:git-Spigot-79a30d7-acbc348]
        at org.bukkit.plugin.SimplePluginManager.callEvent(SimplePluginManager.java:485) [ElenoxServer.jar:git-Spigot-79a30d7-acbc348]
        at net.minecraft.server.v1_12_R1.PlayerConnection.a(PlayerConnection.java:1584) [ElenoxServer.jar:git-Spigot-79a30d7-acbc348]
        at net.minecraft.server.v1_12_R1.PacketPlayInUseEntity.a(SourceFile:69) [ElenoxServer.jar:git-Spigot-79a30d7-acbc348]
        at net.minecraft.server.v1_12_R1.PacketPlayInUseEntity.a(SourceFile:13) [ElenoxServer.jar:git-Spigot-79a30d7-acbc348]
        at net.minecraft.server.v1_12_R1.PlayerConnectionUtils$1.run(SourceFile:13) [ElenoxServer.jar:git-Spigot-79a30d7-acbc348]
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [?:1.8.0_201]
        at java.util.concurrent.FutureTask.run(FutureTask.java:266) [?:1.8.0_201]
        at net.minecraft.server.v1_12_R1.SystemUtils.a(SourceFile:46) [ElenoxServer.jar:git-Spigot-79a30d7-acbc348]
        at net.minecraft.server.v1_12_R1.MinecraftServer.D(MinecraftServer.java:748) [ElenoxServer.jar:git-Spigot-79a30d7-acbc348]
        at net.minecraft.server.v1_12_R1.DedicatedServer.D(DedicatedServer.java:406) [ElenoxServer.jar:git-Spigot-79a30d7-acbc348]
        at net.minecraft.server.v1_12_R1.MinecraftServer.C(MinecraftServer.java:679) [ElenoxServer.jar:git-Spigot-79a30d7-acbc348]
        at net.minecraft.server.v1_12_R1.MinecraftServer.run(MinecraftServer.java:577) [ElenoxServer.jar:git-Spigot-79a30d7-acbc348]
        at java.lang.Thread.run(Thread.java:748) [?:1.8.0_201]
    Caused by: java.lang.NullPointerException
        at net.minecraft.server.v1_12_R1.MerchantRecipeList.a(SourceFile:83) ~[ElenoxServer.jar:git-Spigot-79a30d7-acbc348]
        at com.elenox.core.api.merchant.v1_12.Merchant.addCustomer(Merchant.java:288) ~[?:?]
        at com.elenox.core.listeners.player.PlayerInteract.onInteractEntity(PlayerInteract.java:58) ~[?:?]
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_201]
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_201]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_201]
        at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_201]
        at org.bukkit.plugin.java.JavaPluginLoader$1.execute(JavaPluginLoader.java:302) ~[ElenoxServer.jar:git-Spigot-79a30d7-acbc348]
        ... 15 more

    Code (Text):

    private final MerchantRecipeList offers = new MerchantRecipeList();
     @Override
        public boolean addCustomer(Player player) {
            checkNotNull(player, "player");

            if (this.customers.add(player)) {
                final EntityPlayer player0 = ((CraftPlayer) player).getHandle();
                Container container0 = null;

                try {
                    container0 = new ContainerMerchant(player0, this);
                    container0 = CraftEventFactory.callInventoryOpenEvent(player0, container0);
                } catch (Exception e) {
                    e.printStackTrace();
                }

                if (container0 == null) {
                    this.customers.remove(player);
                    return false;
                }

                final int window = player0.nextContainerCounter();

                player0.activeContainer = container0;
                player0.activeContainer.windowId = window;
                player0.activeContainer.addSlotListener(player0);

                // Open the window
                player0.playerConnection.sendPacket(new PacketPlayOutOpenWindow(window, "minecraft:villager", this.sendTitle, 0));

                Preconditions.checkNotNull(this.offers, "offers is null !");
             
                // Write the recipe list
                final PacketDataSerializer content = new PacketDataSerializer(Unpooled.buffer());
                content.writeInt(window);
                this.offers.a(content);

                // Send the offers
                player0.playerConnection.sendPacket(new PacketPlayOutCustomPayload("MC|TrList", content));

                return true;
            }

            return false;
        }

    LINE 288:
    Code (Text):
    this.offers.a(content);
     
    #1 Xx_Will33_xX, Mar 17, 2019 at 2:03 PM
    Last edited: Mar 17, 2019 at 4:22 PM
  2. U tried debugging? Do a System.out as either offers or content must be null.
    I think content is null. Just look into the method 'a' and see if it might not match the requirements or you might need to do something else first, before being able to use the 'a' method.
     
  3. What are you trying to do exactly?
    If it's open up a villager with custom trades, why are you using NMS when Bukkit has the appropriate API?

    You could simply use Merchant to add the recipes you're looking for and then use HumanEntity#openInventory to open Villager#getInventory.


    As for the specific error, you'd have to look into the source of MerchantRecipeList as that's where the actuall NPE occurs. The recent 1.12.2 code I just got from BuildTools doesn't match the line numbers of yours so I'm not sure what the issue is:
    Code (Java):

        public void a(PacketDataSerializer var1) {
            var1.writeByte((byte) (this.size() & 255));
            for (int var2 = 0; var2 < this.size(); ++var2) {
                MerchantRecipe var3 = (MerchantRecipe) this.get(var2);
                var1.a(var3.getBuyItem1());
                var1.a(var3.getBuyItem3());
                ItemStack var4 = var3.getBuyItem2();
                var1.writeBoolean(!var4.isEmpty());
                if (!var4.isEmpty()) {
                    var1.a(var4);
                }
                var1.writeBoolean(var3.h());
                var1.writeInt(var3.e());
                var1.writeInt(var3.f());
            }
        }
     
  4. md_5

    Administrator Developer

    Since the file isn't used by Spigot the line numbers won't match as the stack trace gives the original Mojang line numbers, not the Spigot decompiled ones.
     
  5. I have changed to:

    Code (Text):

    @Override
    public boolean addCustomer(Player player) {
        checkNotNull(player, "player");

        if (this.customers.add(player)) {
            final EntityPlayer player0 = ((CraftPlayer) player).getHandle();
            player0.openTrade(this);
            return true;
        }

        return false;
    }
     
    But I have the same mistake

    Code (Text):
    Caused by: java.lang.NullPointerException
        at net.minecraft.server.v1_12_R1.MerchantRecipeList.a(SourceFile:83) ~[ElenoxServer.jar:git-Spigot-79a30d7-acbc348]
        at net.minecraft.server.v1_12_R1.EntityPlayer.openTrade(EntityPlayer.java:857) ~[ElenoxServer.jar:git-Spigot-79a30d7-acbc348]
        at com.elenox.core.api.merchant.v1_12.Merchant.addCustomer(Merchant.java:261) ~[?:?]
        at com.elenox.core.listeners.player.PlayerInteract.onInteractEntity(PlayerInteract.java:58) ~[?:?]
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_201]
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_201]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_201]
        at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_201]
        at org.bukkit.plugin.java.JavaPluginLoader$1.execute(JavaPluginLoader.java:302) ~[ElenoxServer.jar:git-Spigot-79a30d7-acbc348]
        ... 15 more
     


    I use the git-Spigot-79a30d7-acbc348 (MC: 1.12.2) version of spigot

    Code (Text):
    public class Merchant implements IMerchant, com.elenox.api.npc.merchant.Merchant {

        // The recipes list
        private final MerchantRecipeList offers = new MerchantRecipeList();

        // The customers
        private final Set<Player> customers = Sets.newHashSet();

        // The title of the merchant
        private String title;
        private boolean jsonTitle;

        // The title that will be send
        private IChatBaseComponent sendTitle;

        // The trade handlers
        final Set<MerchantTradeListener> handlers = Sets.newHashSet();

        // Internal use only
        com.elenox.core.api.merchant.v1_12.MerchantOffer onTrade;
        EntityPlayer onTradePlayer;

        public Merchant(String title, boolean jsonTitle) {
            this.setTitle(title, jsonTitle);
        }

        @Override
        public String getTitle() {
            return this.title;
        }

        @Override
        public boolean isTitleJson() {
            return this.jsonTitle;
        }

        @Override
        public void setTitle(String title, boolean jsonTitle) {
            checkNotNull(title, "title");

            // The old title
            final IChatBaseComponent oldTitle = this.sendTitle;
            final IChatBaseComponent newTitle;

            if (jsonTitle) {
                try {
                    newTitle = ChatSerializer.a(title);
                } catch (Exception e) {
                    throw new IllegalArgumentException("invalid json format (" + title + ")", e);
                }
            } else {
                newTitle = CraftChatMessage.fromString(title)[0];
            }

            this.sendTitle = newTitle;
            this.jsonTitle = jsonTitle;
            this.title = title;

            // Send a update
            if (!this.sendTitle.equals(oldTitle)) {
                sendTitleUpdate();
            }
        }

        @Override
        public void setTitle(String title) {
            this.setTitle(title, false);
        }

        @Override
        public boolean addListener(MerchantTradeListener listener) {
            checkNotNull(listener, "listener");
            return this.handlers.add(listener);
        }

        @Override
        public boolean removeListener(MerchantTradeListener listener) {
            checkNotNull(listener, "listener");
            return this.handlers.remove(listener);
        }

        @Override
        public Collection<MerchantTradeListener> getListeners() {
            return Lists.newArrayList(this.handlers);
        }

        @Override
        public int getOffersCount() {
            return this.offers.size();
        }

        @Override
        public com.elenox.core.api.merchant.v1_12.MerchantOffer getOfferAt(int index) {
            if (index < 0 || index >= this.offers.size()) {
                throw new IndexOutOfBoundsException("index (" + index + ") out of bounds min (0) and max (" + this.offers.size() + ")");
            }
            return (com.elenox.core.api.merchant.v1_12.MerchantOffer) this.offers.get(index);
        }

        @Override
        public void setOfferAt(int index, MerchantOffer offer) {
            checkNotNull(offer, "offer");

            if (index < 0 || index >= this.offers.size()) {
                throw new IndexOutOfBoundsException("index (" + index + ") out of bounds min (0) and max (" + this.offers.size() + ")");
            }

            final com.elenox.core.api.merchant.v1_12.MerchantOffer old = (com.elenox.core.api.merchant.v1_12.MerchantOffer) this.offers.set(index, (MerchantRecipe) offer);
            old.remove(this);

            // Send the new offer list
            sendUpdate();
        }

        @Override
        public void insetOfferAt(int index, MerchantOffer offer) {
            checkNotNull(offer, "offer");

            if (index < 0 || index >= this.offers.size()) {
                throw new IndexOutOfBoundsException("index (" + index + ") out of bounds min (0) and max (" + this.offers.size() + ")");
            }

            this.offers.add(index, (MerchantRecipe) offer);
        }

        @Override
        public void removeOffer(MerchantOffer offer) {
            checkNotNull(offer, "offer");

            //noinspection SuspiciousMethodCalls
            if (this.offers.remove(offer)) {
                // Unlink the offer
                ((com.elenox.core.api.merchant.v1_12.MerchantOffer) offer).remove(this);

                // Send the new offer list
                sendUpdate();
            }
        }

        @Override
        public void removeOffers(Iterable<MerchantOffer> offers) {
            checkNotNull(offers, "offers");

            // Only update if necessary
            if (offers.iterator().hasNext()) {
                return;
            }

            //noinspection SuspiciousMethodCalls
            if (this.offers.removeAll(Lists.newArrayList(offers))) {
                // Unlink the offers
                for (MerchantOffer offer : offers) {
                    ((com.elenox.core.api.merchant.v1_12.MerchantOffer) offer).remove(this);
                }

                // Send the new offer list
                sendUpdate();
            }
        }

        @Override
        public void addOffer(MerchantOffer offer) {
            checkNotNull(offer, "offer");

            //noinspection SuspiciousMethodCalls
            if (this.offers.contains(offer)) {
                return;
            }

            // Add the offer
            this.offers.add((MerchantRecipe) offer);

            // Link the offer
            ((com.elenox.core.api.merchant.v1_12.MerchantOffer) offer).add(this);

            // Send the new offer list
            sendUpdate();
        }

        @Override
        public void addOffers(Iterable<MerchantOffer> offers) {
            checkNotNull(offers, "offers");

            // Only update if necessary
            if (!offers.iterator().hasNext()) {
                return;
            }

            // Add and link the offers
            for (MerchantOffer offer : offers) {
                //noinspection SuspiciousMethodCalls
                if (this.offers.contains(offer)) {
                    continue;
                }
                this.offers.add((MerchantRecipe) offer);
                ((com.elenox.core.api.merchant.v1_12.MerchantOffer) offer).add(this);
            }

            // Send the new offer list
            sendUpdate();
        }

        @Override
        public void sortOffers(final Comparator<MerchantOffer> comparator) {
            checkNotNull(comparator, "comparator");

            // Only sort if necessary
            if (this.offers.size() <= 1) {
                return;
            }

            // Sort the offers
            Collections.sort(this.offers, new Comparator<MerchantRecipe>() {

                @Override
                public int compare(MerchantRecipe arg0, MerchantRecipe arg1) {
                    return comparator.compare((MerchantOffer) arg0, (MerchantOffer) arg1);
                }

            });

            // Send the new offer list
            this.sendUpdate();
        }

        @Override
        public List<MerchantOffer> getOffers() {
            final List<MerchantOffer> offers = Lists.newArrayList();
            for (MerchantRecipe recipe : this.offers) {
                offers.add((MerchantOffer) recipe);
            }
            return offers;
        }

        @Override
        public boolean addCustomer(Player player) {
            checkNotNull(player, "player");

            if (this.customers.add(player)) {
                final EntityPlayer player0 = ((CraftPlayer) player).getHandle();
                player0.openTrade(this);
                return true;
            }

            return false;
        }

        @Override
        public boolean removeCustomer(Player player) {
            checkNotNull(player, "player");

            if (this.customers.remove(player)) {
                player.closeInventory();
                return true;
            }

            return false;
        }

        @Override
        public boolean hasCustomer(Player player) {
            checkNotNull(player, "player");
            return this.customers.contains(player);
        }

        @Override
        public Collection<Player> getCustomers() {
            return Lists.newArrayList(this.customers);
        }
        @Override
        public void setTradingPlayer(EntityHuman entityHuman) {

        }

        @Override
        public EntityHuman getTrader() {
            return null;
        }

        @Override
        public MerchantRecipeList getOffers(EntityHuman human) {
            return this.offers;
        }

        @Override
        public IChatBaseComponent getScoreboardDisplayName() {
            return this.sendTitle;
        }

        @Override
        public World u_() {
            return null;
        }

        @Override
        public BlockPosition v_() {
            return null;
        }

        @Override
        public void a(MerchantRecipe recipe) {
            // Used by the custom merchant result slot
            this.onTrade = (com.elenox.core.api.merchant.v1_12.MerchantOffer) recipe;
        }

        @Override
        public void a(ItemStack itemStack) {

        }

        private void sendTitleUpdate() {
            // Re-send the open window message to update the window name
            for (Player customer : this.customers) {
                final EntityPlayer player0 = ((CraftPlayer) customer).getHandle();
                player0.playerConnection.sendPacket(
                        new PacketPlayOutOpenWindow(player0.activeContainer.windowId, "minecraft:villager", this.sendTitle, 0));
                player0.updateInventory(player0.activeContainer);
            }
        }

        // Called when the merchant requires a update
        void sendUpdate() {
            if (this.customers.isEmpty()) {
                return;
            }
            // Only send if needed
            if (this.onTradePlayer != null && this.customers.size() <= 1) {
                return;
            }

            // Write the recipe list
            final PacketDataSerializer content0 = new PacketDataSerializer(Unpooled.buffer());
            this.offers.a(content0);

            // Send a packet to all the players
            for (Player customer : this.customers) {
                final EntityPlayer player0 = ((CraftPlayer) customer).getHandle();

                // Only send to player that need it
                if (player0 == this.onTradePlayer) {
                    continue;
                }

                // Every player has a different window id
                final PacketDataSerializer content1 = new PacketDataSerializer(Unpooled.buffer());
                content1.writeInt(player0.activeContainer.windowId);
                content1.writeBytes(content0);

                player0.playerConnection.sendPacket(new PacketPlayOutCustomPayload("MC|TrList", content1));
            }
        }
    }
     
  6. I took the BuildTools code to understand where the NPE came from, the NPE came from the second item so I added a condition. I have no more errors, but the inventory opens and it closes instantly: /

    Code (Text):
    player0.playerConnection.sendPacket(new PacketPlayOutOpenWindow(window, "minecraft:villager", this.sendTitle, 0));

                final PacketDataSerializer var1 = new PacketDataSerializer(Unpooled.buffer());
                var1.writeInt(window);
                var1.writeByte ( ( byte ) ( this.offers . size ( ) & 255 ) ) ;
                for ( int var2 = 0 ; var2 < this.offers . size ( ) ; ++ var2 ) {
                    MerchantRecipe var3 = (MerchantRecipe) this.offers.get(var2);
                    var1.a(var3.getBuyItem1());
                    var1.a(var3.getBuyItem3());
                    ItemStack var4 = var3.getBuyItem2();
                    var1.writeBoolean(var4 != null);
                    if(var4 != null) {
                        if (!var4.isEmpty()) {
                            var1.a(var4);
                        }
                    }
                    var1.writeBoolean(var3.h());
                    var1.writeInt(var3.e());
                    var1.writeInt(var3.f());
                }
                player0.playerConnection.sendPacket(new PacketPlayOutCustomPayload("MC|TrList", var1));
     

Share This Page