I want to create a plugin where I have a table and in that I can craft regular crafting recipes and some custom ones. I know that I can create custom Recipes by default but I want to make them only craftable in this "special" table.
So I've got this code for my InventoryClickEvent https://hastebin.com/konehibohi.cs
And my problem is that this is throwing me a NullPointerException when I place an item in one of the specified crafting slots but when I put a second item in the same slot it outputs me the right recipe.
Console Output:
[02:21:51] [Server thread/INFO]: VanillaRL | Enabling VanillaRL...
[02:21:51] [Server thread/INFO]: VanillaRL | Enabled VanillaRL
[02:21:51] [Server thread/INFO]: Server permissions file permissions.yml is empty, ignoring it
[02:21:51] [Server thread/INFO]: F4LS3_: Reload complete.
[02:21:53] [Server thread/INFO]: [null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null]
[02:21:53] [Server thread/ERROR]: Could not pass event InventoryClickEvent to VanillaRL v1.0-SNAPSHOT
org.bukkit.event.EventException: null
at org.bukkit.plugin.java.JavaPluginLoader$1.execute(JavaPluginLoader.java:319) ~[spigot-1.16.1.jar:git-Spigot-0287a20-7560f5f]
at org.bukkit.plugin.RegisteredListener.callEvent(RegisteredListener.java:70) ~[spigot-1.16.1.jar:git-Spigot-0287a20-7560f5f]
at org.bukkit.plugin.SimplePluginManager.fireEvent(SimplePluginManager.java:589) ~[spigot-1.16.1.jar:git-Spigot-0287a20-7560f5f]
at org.bukkit.plugin.SimplePluginManager.callEvent(SimplePluginManager.java:576) ~[spigot-1.16.1.jar:git-Spigot-0287a20-7560f5f]
at net.minecraft.server.v1_16_R1.PlayerConnection.a(PlayerConnection.java:2191) ~[spigot-1.16.1.jar:git-Spigot-0287a20-7560f5f]
at net.minecraft.server.v1_16_R1.PacketPlayInWindowClick.a(SourceFile:32) ~[spigot-1.16.1.jar:git-Spigot-0287a20-7560f5f]
at net.minecraft.server.v1_16_R1.PacketPlayInWindowClick.a(SourceFile:10) ~[spigot-1.16.1.jar:git-Spigot-0287a20-7560f5f]
at net.minecraft.server.v1_16_R1.PlayerConnectionUtils.lambda$0(PlayerConnectionUtils.java:19) ~[spigot-1.16.1.jar:git-Spigot-0287a20-7560f5f]
at net.minecraft.server.v1_16_R1.TickTask.run(SourceFile:18) ~[spigot-1.16.1.jar:git-Spigot-0287a20-7560f5f]
at net.minecraft.server.v1_16_R1.IAsyncTaskHandler.executeTask(SourceFile:144) ~[spigot-1.16.1.jar:git-Spigot-0287a20-7560f5f]
at net.minecraft.server.v1_16_R1.IAsyncTaskHandlerReentrant.executeTask(SourceFile:23) ~[spigot-1.16.1.jar:git-Spigot-0287a20-7560f5f]
at net.minecraft.server.v1_16_R1.IAsyncTaskHandler.executeNext(SourceFile:118) ~[spigot-1.16.1.jar:git-Spigot-0287a20-7560f5f]
at net.minecraft.server.v1_16_R1.MinecraftServer.aZ(MinecraftServer.java:943) ~[spigot-1.16.1.jar:git-Spigot-0287a20-7560f5f]
at net.minecraft.server.v1_16_R1.MinecraftServer.executeNext(MinecraftServer.java:936) ~[spigot-1.16.1.jar:git-Spigot-0287a20-7560f5f]
at net.minecraft.server.v1_16_R1.IAsyncTaskHandler.awaitTasks(SourceFile:127) ~[spigot-1.16.1.jar:git-Spigot-0287a20-7560f5f]
at net.minecraft.server.v1_16_R1.MinecraftServer.sleepForTick(MinecraftServer.java:920) ~[spigot-1.16.1.jar:git-Spigot-0287a20-7560f5f]
at net.minecraft.server.v1_16_R1.MinecraftServer.v(MinecraftServer.java:852) ~[spigot-1.16.1.jar:git-Spigot-0287a20-7560f5f]
at net.minecraft.server.v1_16_R1.MinecraftServer.lambda$0(MinecraftServer.java:164) ~[spigot-1.16.1.jar:git-Spigot-0287a20-7560f5f]
at java.lang.Thread.run(Thread.java:834) [?:?]
Caused by: java.lang.IllegalArgumentException: Result cannot be null
at org.apache.commons.lang.Validate.notNull(Validate.java:192) ~[spigot-1.16.1.jar:git-Spigot-0287a20-7560f5f]
at org.bukkit.craftbukkit.v1_16_R1.CraftServer.getRecipesFor(CraftServer.java:1214) ~[spigot-1.16.1.jar:git-Spigot-0287a20-7560f5f]
at org.bukkit.Bukkit.getRecipesFor(Bukkit.java:717) ~[spigot-1.16.1.jar:git-Spigot-0287a20-7560f5f]
at de.f4ls3.vanillarl.events.InventoryClickEvent.onInventoryClick(InventoryClickEvent.java:34) ~[?:?]
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?]
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:?]
at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]
at java.lang.reflect.Method.invoke(Method.java:566) ~[?:?]
at org.bukkit.plugin.java.JavaPluginLoader$1.execute(JavaPluginLoader.java:315) ~[spigot-1.16.1.jar:git-Spigot-0287a20-7560f5f]
... 18 more
[02:21:54] [Server thread/INFO]: [null, null, null, null, null, null, null, null, null, null, ItemStack{OAK_PLANKS x 1}, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null]
[02:21:54] [Server thread/INFO]: [org.bukkit.craftbukkit.v1_16_R1.inventory.CraftShapelessRecipe@c41eafb]
For some more info about all the classes you can check my repo https://github.com/F4LS3/VanillaRL
Thanks in advance!
I have found the source of the problem. It relies on the way the Spigot/Bukkit API handles events.
Why does this happen?
Here you have an explanation of why this may be happening.
Basic definition of an event
In Minecraft, an event wraps an action that the client has performed and it sends it to the server. In this way when you perform an action the server will know it and will be able to pass this event to other clients (other online players).
What happens when an event is not caught by an event handler?
If you don't implement an event handler, then an event will be processed in the following way:
Your client ----------------> The server --------------> All clients (including you)
Requests an action Applies the action Recieve the action
What happens when you catch an event in an event handler?
On the other hand, if you implement an event handler, you are intercepting an event on the server side so the event flow will be:
Your client ----------\ /---> The server -----> All clients
\-- Event handler --/
In the event handler, the server is waiting for you to handle the event before applying it to all clients.
On an InventoryClickEvent you may be modifying the inventory contents, but those changes aren't applied until your handler ends handling the event.
So if you look for the inventory contents during the event, you will be given the inventory contents before the user clicked. As the server hasn't processed the event yet.
If you then take the contents of a previously empty slot, the content will be null
as the new Item hasn't been processed by the server yet. Then, when you take out the item, the content will return the item as the server doesn't know that the slot is empty yet.
My solution
Schedule an asynchronous delayed task so that your event handler will exit and the server will process the event before you checking the items the user placed:
Your client ----------\ /---> The server -----> All clients
\-- Event handler --/
\- Schedule async task -> Actual handling
The code I have implemented is the following:
public class InventoryClickEvent implements Listener
{
private final VanillaRL plugin;
public InventoryClickEvent(VanillaRL plugin) {
this.plugin = plugin;
}
@EventHandler
public void onInventoryClick(org.bukkit.event.inventory.InventoryClickEvent event)
{
new BukkitRunnable(){
@Override
public void run() {
delayedHandler(event);
}
}.runTaskLaterAsynchronously(plugin, 2);
}
private void delayedHandler(org.bukkit.event.inventory.InventoryClickEvent event) {
if(event.getWhoClicked() instanceof Player)
{
Player player = (Player) event.getWhoClicked();
if (CraftingManager.hasInventory(event.getClickedInventory()))
{
CraftingInventory craftingInventory = CraftingManager.getCraftingInventories().stream().filter(craftingInventory1 -> craftingInventory1.getInventory().equals(event.getClickedInventory())).findFirst().get();
System.out.println(Arrays.toString(craftingInventory.getInventory().getContents()));
if (craftingInventory.getCraftingSlots().contains(event.getSlot()))
{
if (event.getAction().equals(InventoryAction.PLACE_ALL) || event.getAction().equals(InventoryAction.PLACE_ONE) || event.getAction().equals(InventoryAction.PLACE_SOME))
{
System.out.println(Bukkit.getRecipesFor(craftingInventory.getItem(event.getSlot())));
}
}
}
}
}
}
Then on the main class when you register the event listener:
Bukkit.getPluginManager().registerEvents(new InventoryClickEvent(this), this);
Server output:
[12:48:34] [Craft Scheduler Thread - 0/INFO]: [null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, ItemStack{REDSTONE_BLOCK x 61}, null, null, null, null, null, null, null, null]
[12:48:34] [Craft Scheduler Thread - 0/INFO]: [org.bukkit.craftbukkit.v1_16_R1.inventory.CraftShapedRecipe@709b045a]
Let me know if this works for you!