diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d113c48b..1549887fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,7 +60,7 @@ * Fixed #2650 * Fixed Slimefun items applying damage to items with an `unbreakable` tag * Fixed #2930 -* Fixed #2837 +* Fixed #2926 ## Release Candidate 21 (14 Mar 2021) https://thebusybiscuit.github.io/builds/TheBusyBiscuit/Slimefun4/stable/#21 diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/autocrafters/AbstractAutoCrafter.java b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/autocrafters/AbstractAutoCrafter.java index dddcb63af..86b9dde7d 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/autocrafters/AbstractAutoCrafter.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/autocrafters/AbstractAutoCrafter.java @@ -1,6 +1,8 @@ package io.github.thebusybiscuit.slimefun4.implementation.items.autocrafters; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.function.Predicate; @@ -24,9 +26,9 @@ import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; import io.github.thebusybiscuit.cscorelib2.data.PersistentDataAPI; -import io.github.thebusybiscuit.cscorelib2.inventory.ItemUtils; import io.github.thebusybiscuit.cscorelib2.item.CustomItem; import io.github.thebusybiscuit.cscorelib2.protection.ProtectableAction; +import io.github.thebusybiscuit.slimefun4.api.MinecraftVersion; import io.github.thebusybiscuit.slimefun4.api.SlimefunAddon; import io.github.thebusybiscuit.slimefun4.api.items.ItemState; import io.github.thebusybiscuit.slimefun4.core.attributes.EnergyNetComponent; @@ -415,6 +417,7 @@ public abstract class AbstractAutoCrafter extends SlimefunItem implements Energy // Check if we have an empty slot if (inv.firstEmpty() != -1) { Map itemQuantities = new HashMap<>(); + List leftoverItems = new ArrayList<>(); for (Predicate predicate : recipe.getIngredients()) { // Check if any Item matches the Predicate @@ -429,19 +432,71 @@ public abstract class AbstractAutoCrafter extends SlimefunItem implements Energy // Double-check to be extra safe if (item != null) { - // Consume the difference - int toRemove = item.getAmount() - entry.getValue(); - ItemUtils.consumeItem(item, toRemove, true); + // Handle leftovers + ItemStack leftover = getLeftoverItem(item); + + if (leftover != null) { + // Account for the amount of removed items + leftover.setAmount(item.getAmount() - entry.getValue()); + leftoverItems.add(leftover); + } + + // Update the item amount + item.setAmount(entry.getValue()); } } - // All Predicates have found a match - return inv.addItem(recipe.getResult().clone()).isEmpty(); + boolean success = inv.addItem(recipe.getResult().clone()).isEmpty(); + + if (success) { + // Fixes #2926 - Push leftover items to the inventory. + for (ItemStack leftoverItem : leftoverItems) { + inv.addItem(leftoverItem); + } + } + + return success; } return false; } + /** + * This method returns the "leftovers" from a crafting operation. + * The method functions very similarly to {@link Material#getCraftingRemainingItem()}. + * However we cannot use this method as it is only available in the latest 1.16 snapshots + * of Spigot, not even on earlier 1.16 builds... + * But this gives us more control over the leftovers anyway! + * + * @param item + * The {@link ItemStack} that is being consumed + * + * @return The leftover item or null if the item is fully consumed + */ + @Nullable + private ItemStack getLeftoverItem(@Nonnull ItemStack item) { + Material type = item.getType(); + + switch (type) { + case WATER_BUCKET: + case LAVA_BUCKET: + case MILK_BUCKET: + return new ItemStack(Material.BUCKET); + case DRAGON_BREATH: + case POTION: + return new ItemStack(Material.GLASS_BOTTLE); + default: + MinecraftVersion minecraftVersion = SlimefunPlugin.getMinecraftVersion(); + + // Honey does not exist in 1.14 + if (minecraftVersion.isAtLeast(MinecraftVersion.MINECRAFT_1_15) && type == Material.HONEY_BOTTLE) { + return new ItemStack(Material.GLASS_BOTTLE); + } else { + return null; + } + } + } + /** * This method returns the max amount of electricity this machine can hold. * diff --git a/src/test/java/io/github/thebusybiscuit/slimefun4/testing/tests/items/autocrafters/TestAutoCrafter.java b/src/test/java/io/github/thebusybiscuit/slimefun4/testing/tests/items/autocrafters/TestAutoCrafter.java index 9748d255e..8770c1045 100644 --- a/src/test/java/io/github/thebusybiscuit/slimefun4/testing/tests/items/autocrafters/TestAutoCrafter.java +++ b/src/test/java/io/github/thebusybiscuit/slimefun4/testing/tests/items/autocrafters/TestAutoCrafter.java @@ -98,6 +98,29 @@ class TestAutoCrafter { Assertions.assertFalse(inv.containsAtLeast(result, 1)); } + @Test + @DisplayName("Test resource leftovers when crafting") + void testResourceLeftovers() { + NamespacedKey key = new NamespacedKey(plugin, "resource_leftovers_test"); + ItemStack result = new CustomItem(Material.DIAMOND, "&9Diamond. Nuff said."); + ShapelessRecipe recipe = new ShapelessRecipe(key, result); + recipe.addIngredient(new MaterialChoice(Material.HONEY_BOTTLE)); + recipe.addIngredient(new MaterialChoice(Material.HONEY_BOTTLE)); + + AbstractRecipe abstractRecipe = AbstractRecipe.of(recipe); + AbstractAutoCrafter crafter = getVanillaAutoCrafter(); + InventoryMock inv = new ChestInventoryMock(null, 9); + + inv.addItem(new ItemStack(Material.HONEY_BOTTLE, 2)); + Assertions.assertTrue(crafter.craft(inv, abstractRecipe)); + + Assertions.assertFalse(inv.contains(Material.HONEY_BOTTLE, 2)); + Assertions.assertTrue(inv.containsAtLeast(result, 1)); + + // Check for leftovers + Assertions.assertTrue(inv.contains(Material.GLASS_BOTTLE, 2)); + } + @Test @DisplayName("Test crafting an invalid ShapelessRecipe") void testInvalidShapelessRecipe() {