diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/core/networks/cargo/CargoUtils.java b/src/main/java/io/github/thebusybiscuit/slimefun4/core/networks/cargo/CargoUtils.java index b7e5fabab..605f2b333 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/core/networks/cargo/CargoUtils.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/core/networks/cargo/CargoUtils.java @@ -32,8 +32,10 @@ import me.mrCookieSlime.Slimefun.api.item_transport.ItemTransportFlow; final class CargoUtils { - // Whitelist or blacklist slots - private static final int[] FILTER_SLOTS = { 19, 20, 21, 28, 29, 30, 37, 38, 39 }; + /** + * These are the slots where our filter items sit. + */ + static final int[] FILTER_SLOTS = { 19, 20, 21, 28, 29, 30, 37, 38, 39 }; private CargoUtils() {} @@ -391,7 +393,7 @@ final class CargoUtils { } } - // Check if there are event non-air items + // Check if there are any non-air items if (itemsToCompare > 0) { // Only create the Wrapper if its worth it if (itemsToCompare > 1) { diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/core/networks/cargo/ItemFilter.java b/src/main/java/io/github/thebusybiscuit/slimefun4/core/networks/cargo/ItemFilter.java new file mode 100644 index 000000000..099af4fed --- /dev/null +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/core/networks/cargo/ItemFilter.java @@ -0,0 +1,173 @@ +package io.github.thebusybiscuit.slimefun4.core.networks.cargo; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.Predicate; + +import javax.annotation.Nonnull; + +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.inventory.ItemStack; + +import io.github.thebusybiscuit.slimefun4.utils.SlimefunUtils; +import io.github.thebusybiscuit.slimefun4.utils.itemstack.ItemStackWrapper; +import me.mrCookieSlime.CSCoreLibPlugin.Configuration.Config; +import me.mrCookieSlime.Slimefun.Objects.SlimefunItem.SlimefunItem; +import me.mrCookieSlime.Slimefun.api.BlockStorage; +import me.mrCookieSlime.Slimefun.api.inventory.BlockMenu; + +/** + * The {@link ItemFilter} is a performance-optimization for our {@link CargoNet}. + * It is a snapshot of a cargo node's configuration. + * + * @author TheBusyBiscuit + * + */ +class ItemFilter implements Predicate { + + /** + * Our {@link List} of items to check against, might be empty. + */ + private final List items = new ArrayList<>(); + + /** + * Our default value for this {@link ItemFilter}. + * A default value of {@literal true} will mean that it returns true if no + * match was found. It will deny any items that match. + * A default value of {@literal false} means that it will return false if no + * match was found. Only items that match will make it past this {@link ItemFilter}. + */ + private boolean defaultValue; + + /** + * Whether we should also compare the lore. + */ + private boolean checkLore; + + /** + * If an {@link ItemFilter} is marked as dirty / outdated, then it will be updated + * on the next tick. + */ + private boolean dirty = false; + + /** + * This creates a new {@link ItemFilter} for the given {@link Block}. + * This will copy all settings from that {@link Block} to this filter. + * + * @param b + * The {@link Block} + */ + public ItemFilter(@Nonnull Block b) { + update(b); + } + + /** + * This updates or refreshes the {@link ItemFilter} to copy the settings + * from the given {@link Block}. It takes a new snapshot. + * + * @param b + * The {@link Block} + */ + public void update(@Nonnull Block b) { + // Store the returned Config instance to avoid heavy calls + Config blockData = BlockStorage.getLocationInfo(b.getLocation()); + String id = blockData.getString("id"); + SlimefunItem item = SlimefunItem.getByID(id); + BlockMenu menu = BlockStorage.getInventory(b.getLocation()); + + if (item == null || menu == null) { + // Don't filter for a non-existing item (safety check) + clear(false); + } else if (id.equals("CARGO_NODE_OUTPUT")) { + // Output Nodes have no filter, allow everything + clear(true); + } else { + this.items.clear(); + this.checkLore = Objects.equals(blockData.getString("filter-lore"), "true"); + this.defaultValue = !Objects.equals(blockData.getString("filter-type"), "whitelist"); + + for (int slot : CargoUtils.FILTER_SLOTS) { + ItemStack stack = menu.getItemInSlot(slot); + + if (stack != null && stack.getType() != Material.AIR) { + this.items.add(new ItemStackWrapper(stack)); + } + } + } + } + + /** + * This will clear the {@link ItemFilter} and reject any + * {@link ItemStack}. + * + * @param defaultValue + * The new default value. + */ + private void clear(boolean defaultValue) { + this.items.clear(); + this.checkLore = false; + this.defaultValue = defaultValue; + } + + /** + * Whether this {@link ItemFilter} is outdated and needs to be refreshed. + * + * @return Whether the filter is outdated. + */ + public boolean isDirty() { + return dirty; + } + + /** + * This marks this {@link ItemFilter} as dirty / outdated. + */ + public void markDirty() { + this.dirty = true; + } + + @Override + public boolean test(@Nonnull ItemStack item) { + // An empty Filter does not need to be iterated over. + if (items.isEmpty()) { + return defaultValue; + } + + int potentialMatches = 0; + + // This is a first check for materials to see if we might even have any match. + // If there is no potential match then we won't need to perform the quite + // intense operation .getItemMeta() + for (ItemStackWrapper stack : items) { + if (stack.getType() == item.getType()) { + // We found a potential match based on the Material + potentialMatches++; + } + } + + if (potentialMatches == 0) { + // If there is no match, we can safely assume the default value + return defaultValue; + } else { + // If there is more than one potential match, create a wrapper to save + // performance on the Item Meta otherwise just use the item directly. + ItemStack subject = potentialMatches == 1 ? item : new ItemStackWrapper(item); + + // If there is only one match, we won't need to create a Wrapper + // and thus only perform .getItemMeta() once + for (ItemStackWrapper stack : items) { + if (SlimefunUtils.isItemSimilar(subject, stack, checkLore, false)) { + // The filter has found a match, we can return the opposite + // of our default value. If we exclude items, this is where we + // would return false. Otherwise we return true. + return !defaultValue; + } + } + + // If no particular item was matched, we fallback to the default value. + return defaultValue; + } + } + +}