From 3678d9e8c9c78141d5df599d58f4e06aa3344303 Mon Sep 17 00:00:00 2001 From: Aquaholic Date: Thu, 1 Jan 2026 16:36:56 +0800 Subject: [PATCH 1/2] adds tag filtering to black hole chest as well as the ability to deny items of a tag --- .../chests/BlackHoleChestBlockEntity.java | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/src/main/java/de/dafuqs/spectrum/blocks/chests/BlackHoleChestBlockEntity.java b/src/main/java/de/dafuqs/spectrum/blocks/chests/BlackHoleChestBlockEntity.java index b3475992df..ddf0c33fca 100644 --- a/src/main/java/de/dafuqs/spectrum/blocks/chests/BlackHoleChestBlockEntity.java +++ b/src/main/java/de/dafuqs/spectrum/blocks/chests/BlackHoleChestBlockEntity.java @@ -1,5 +1,6 @@ package de.dafuqs.spectrum.blocks.chests; +import de.dafuqs.spectrum.*; import de.dafuqs.spectrum.api.block.*; import de.dafuqs.spectrum.api.item.*; import de.dafuqs.spectrum.events.*; @@ -9,6 +10,7 @@ import de.dafuqs.spectrum.networking.*; import de.dafuqs.spectrum.particle.*; import de.dafuqs.spectrum.registries.*; +import it.unimi.dsi.fastutil.objects.*; import net.fabricmc.fabric.api.screenhandler.v1.*; import net.fabricmc.fabric.api.transfer.v1.item.*; import net.minecraft.block.*; @@ -18,16 +20,20 @@ import net.minecraft.item.*; import net.minecraft.nbt.*; import net.minecraft.network.*; +import net.minecraft.registry.*; +import net.minecraft.registry.tag.*; import net.minecraft.screen.*; import net.minecraft.server.network.*; import net.minecraft.server.world.*; import net.minecraft.sound.*; import net.minecraft.text.*; +import net.minecraft.util.*; import net.minecraft.util.collection.*; import net.minecraft.util.math.*; import net.minecraft.world.*; import net.minecraft.world.event.*; import net.minecraft.world.event.listener.*; +import org.apache.commons.lang3.*; import org.jetbrains.annotations.*; import java.util.*; @@ -46,12 +52,15 @@ public class BlackHoleChestBlockEntity extends SpectrumChestBlockEntity implemen private boolean isOpen, isFull, hasXPStorage; float storageTarget, storagePos, lastStorageTarget, capTarget, capPos, lastCapTarget, orbTarget, orbPos, lastOrbTarget, yawTarget, orbYaw, lastYawTarget; long interpTicks, interpLength = 1, age, storedXP, maxStoredXP; - + + private final Object2BooleanMap> filteredTags; + private boolean allTagsDeny = true; public BlackHoleChestBlockEntity(BlockPos blockPos, BlockState blockState) { super(SpectrumBlockEntities.BLACK_HOLE_CHEST, blockPos, blockState); this.itemAndExperienceEventQueue = new ItemAndExperienceEventQueue(new BlockPositionSource(this.pos), RANGE, this); this.filterItems = DefaultedList.ofSize(ITEM_FILTER_SLOT_COUNT, ItemVariant.blank()); + this.filteredTags = new Object2BooleanArrayMap<>(); } public static void tick(@NotNull World world, BlockPos pos, BlockState state, BlackHoleChestBlockEntity chest) { @@ -227,6 +236,7 @@ public void writeNbt(NbtCompound tag) { public void readNbt(NbtCompound tag) { super.readNbt(tag); FilterConfigurable.readFilterNbt(tag, filterItems); + this.updateFilteredTags(); age = tag.getLong("age"); } @@ -322,14 +332,47 @@ public List getItemFilters() { public void setFilterItem(int slot, ItemVariant item) { this.filterItems.set(slot, item); + this.updateFilteredTags(); this.markDirty(); } + public void updateFilteredTags() { + filteredTags.clear(); + this.allTagsDeny = true; + this.getItemFilters().forEach((itemVariant) -> { + ItemStack stack = itemVariant.toStack(); + String name = StringUtils.trim(stack.getName().getString()); + boolean allow = !name.startsWith("!"); + Identifier identifier = Identifier.tryParse(StringUtils.remove(allow ? name : name.substring(1), '#')); + if(identifier == null) { return; } + + // Copied from PastelNodeBlockEntity. This entire section could potentially be a candidate to move into its own function. + TagKey tag = SpectrumCommon.CACHED_ITEM_TAG_MAP.computeIfAbsent(identifier, tagId -> Registries.ITEM.streamTags() + .filter(t -> t.id().equals(tagId)) + .findFirst() + .orElse(null)); + + if(tag == null) { return; } + if(allow) { this.allTagsDeny = false; } + this.filteredTags.put(tag, allow); + }); + } + public boolean acceptsItemStack(ItemStack itemStack) { if (itemStack.isEmpty()) { return false; } + if (!this.filteredTags.isEmpty()) { + int returnValue = 0; + // Latest takes precedence. + for(TagKey tag : this.filteredTags.keySet()) { + if(itemStack.isIn(tag)) { returnValue = this.filteredTags.getBoolean(tag) ? 1 : -1; } + } + if(returnValue != 0) { return returnValue == 1; } + if(this.allTagsDeny) { return true; } + } + boolean allAir = true; for (int i = 0; i < ITEM_FILTER_SLOT_COUNT; i++) { ItemVariant filterItem = this.filterItems.get(i); From 5e62deda9857a8f98c17765340113780d3e82be9 Mon Sep 17 00:00:00 2001 From: Aquaholic Date: Thu, 1 Jan 2026 19:31:59 +0800 Subject: [PATCH 2/2] Propogate change to Pastel Nodes. Choice was made not to check NBT with Black Hole Chest due to it being a potential expensive operation, however, this would be trivial to add. In theory this also speeds up Pastel Nodes doing tag-based filtering at the cost of a marginal increase in storage required. --- .../api/block/TagFilteringInventory.java | 83 +++++++++++++++++++ .../chests/BlackHoleChestBlockEntity.java | 59 ++----------- .../nodes/PastelNodeBlockEntity.java | 44 ++++------ 3 files changed, 105 insertions(+), 81 deletions(-) create mode 100644 src/main/java/de/dafuqs/spectrum/api/block/TagFilteringInventory.java diff --git a/src/main/java/de/dafuqs/spectrum/api/block/TagFilteringInventory.java b/src/main/java/de/dafuqs/spectrum/api/block/TagFilteringInventory.java new file mode 100644 index 0000000000..e723d0f0a4 --- /dev/null +++ b/src/main/java/de/dafuqs/spectrum/api/block/TagFilteringInventory.java @@ -0,0 +1,83 @@ +package de.dafuqs.spectrum.api.block; + +import de.dafuqs.spectrum.*; +import de.dafuqs.spectrum.registries.*; +import it.unimi.dsi.fastutil.objects.*; +import net.fabricmc.fabric.api.transfer.v1.item.*; +import net.minecraft.item.*; +import net.minecraft.registry.*; +import net.minecraft.registry.tag.*; +import net.minecraft.util.*; +import org.apache.commons.lang3.*; + +import java.util.*; + +@SuppressWarnings("UnstableApiUsage") +public interface TagFilteringInventory { + + List getItemFilters(); + default Object2BooleanMap> getFilteredTags() { return Object2BooleanMaps.emptyMap(); } + default boolean onlyDenyListTags() { return true; } + default void setOnlyDenyListTags(boolean onlyDenyListTags) { } + + default boolean acceptsItem(Item item) { + if (item == null || item.equals(Items.AIR)) { return false; } + + if (!this.getFilteredTags().isEmpty()) { + int returnValue = 0; + // Latest takes precedence. 1 for if it's allowed, -1 for if it's denied. + for(TagKey tag : this.getFilteredTags().keySet()) { + if(item.getRegistryEntry().isIn(tag)) { returnValue = this.getFilteredTags().getBoolean(tag) ? 1 : -1; } + } + if(returnValue != 0) { return returnValue == 1; } + // If we only have denyList tags, treat not being in any as am implicit c:everything. + if(this.onlyDenyListTags()) { return true; } + } + + boolean allAir = true; + for (ItemVariant filterItem: this.getItemFilters()) { + if (filterItem.getItem().equals(item)) { return true; } + if (!filterItem.getItem().equals(Items.AIR)) { allAir = false; } + } + return allAir; + } + + // Run on NBT read. + default void clearFilters() { + this.getFilteredTags().clear(); + this.setOnlyDenyListTags(true); + } + + default boolean addTagFilteringItem(ItemVariant itemVariant) { + ItemStack stack = itemVariant.toStack(); + if (!stack.hasCustomName() || !stack.isIn(SpectrumItemTags.TAG_FILTERING_ITEMS)) { + this.setOnlyDenyListTags(false); + return false; + } + String name = StringUtils.trim(stack.getName().getString()); + if (StringUtils.equalsAnyIgnoreCase(name, "*", "any", "all", "everything", "c:*", "c:any", "c:all", "c:everything")) { + this.setOnlyDenyListTags(true); + return true; + } + + boolean allow = !name.startsWith("!"); + Identifier identifier = Identifier.tryParse(StringUtils.remove(allow ? name : name.substring(1), '#')); + if(identifier == null) { return false; } + + // Copied from PastelNodeBlockEntity. This entire section could potentially be a candidate to move into its own function. + TagKey tag = SpectrumCommon.CACHED_ITEM_TAG_MAP.computeIfAbsent(identifier, tagId -> Registries.ITEM.streamTags() + .filter(t -> t.id().equals(tagId)) + .findFirst() + .orElse(null)); + + if(tag == null) { return false; } + if(allow) { this.setOnlyDenyListTags(false); } + return this.getFilteredTags().put(tag, allow); + } + + // Call on change. + default void updateTagFilteringItems() { + this.clearFilters(); + this.getItemFilters().forEach(this::addTagFilteringItem); + } +} diff --git a/src/main/java/de/dafuqs/spectrum/blocks/chests/BlackHoleChestBlockEntity.java b/src/main/java/de/dafuqs/spectrum/blocks/chests/BlackHoleChestBlockEntity.java index ddf0c33fca..e7a0afe994 100644 --- a/src/main/java/de/dafuqs/spectrum/blocks/chests/BlackHoleChestBlockEntity.java +++ b/src/main/java/de/dafuqs/spectrum/blocks/chests/BlackHoleChestBlockEntity.java @@ -40,7 +40,7 @@ import java.util.stream.*; @SuppressWarnings("UnstableApiUsage") -public class BlackHoleChestBlockEntity extends SpectrumChestBlockEntity implements ExtendedScreenHandlerFactory, SidedInventory, EventQueue.Callback { +public class BlackHoleChestBlockEntity extends SpectrumChestBlockEntity implements ExtendedScreenHandlerFactory, SidedInventory, EventQueue.Callback, TagFilteringInventory { public static final int INVENTORY_SIZE = 28; public static final int ITEM_FILTER_SLOT_COUNT = 5; @@ -236,7 +236,7 @@ public void writeNbt(NbtCompound tag) { public void readNbt(NbtCompound tag) { super.readNbt(tag); FilterConfigurable.readFilterNbt(tag, filterItems); - this.updateFilteredTags(); + this.updateTagFilteringItems(); age = tag.getLong("age"); } @@ -278,7 +278,7 @@ public void triggerEvent(World world, GameEventListener listener, Object entry) } } else if (entry instanceof ItemEntityEventQueue.EventEntry itemEntry) { ItemEntity itemEntity = itemEntry.itemEntity; - if (itemEntity != null && itemEntity.isAlive() && !itemEntity.cannotPickup() && acceptsItemStack(itemEntity.getStack())) { + if (itemEntity != null && itemEntity.isAlive() && !itemEntity.cannotPickup() && this.acceptsItem(itemEntity.getStack().getItem())) { int previousAmount = itemEntity.getStack().getCount(); ItemStack remainingStack = InventoryHelper.smartAddToInventory(itemEntity.getStack(), this, Direction.UP); @@ -332,58 +332,13 @@ public List getItemFilters() { public void setFilterItem(int slot, ItemVariant item) { this.filterItems.set(slot, item); - this.updateFilteredTags(); + this.updateTagFilteringItems(); this.markDirty(); } - public void updateFilteredTags() { - filteredTags.clear(); - this.allTagsDeny = true; - this.getItemFilters().forEach((itemVariant) -> { - ItemStack stack = itemVariant.toStack(); - String name = StringUtils.trim(stack.getName().getString()); - boolean allow = !name.startsWith("!"); - Identifier identifier = Identifier.tryParse(StringUtils.remove(allow ? name : name.substring(1), '#')); - if(identifier == null) { return; } - - // Copied from PastelNodeBlockEntity. This entire section could potentially be a candidate to move into its own function. - TagKey tag = SpectrumCommon.CACHED_ITEM_TAG_MAP.computeIfAbsent(identifier, tagId -> Registries.ITEM.streamTags() - .filter(t -> t.id().equals(tagId)) - .findFirst() - .orElse(null)); - - if(tag == null) { return; } - if(allow) { this.allTagsDeny = false; } - this.filteredTags.put(tag, allow); - }); - } - - public boolean acceptsItemStack(ItemStack itemStack) { - if (itemStack.isEmpty()) { - return false; - } - - if (!this.filteredTags.isEmpty()) { - int returnValue = 0; - // Latest takes precedence. - for(TagKey tag : this.filteredTags.keySet()) { - if(itemStack.isIn(tag)) { returnValue = this.filteredTags.getBoolean(tag) ? 1 : -1; } - } - if(returnValue != 0) { return returnValue == 1; } - if(this.allTagsDeny) { return true; } - } - - boolean allAir = true; - for (int i = 0; i < ITEM_FILTER_SLOT_COUNT; i++) { - ItemVariant filterItem = this.filterItems.get(i); - if (filterItem.getItem().equals(itemStack.getItem())) { - return true; - } else if (!filterItem.getItem().equals(Items.AIR)) { - allAir = false; - } - } - return allAir; - } + public Object2BooleanMap> getFilteredTags() { return this.filteredTags; } + public boolean onlyDenyListTags() { return this.allTagsDeny; } + public void setOnlyDenyListTags(boolean onlyDenyListTags) { this.allTagsDeny = onlyDenyListTags; } public boolean hasExperienceStorageItem() { return this.inventory.get(EXPERIENCE_STORAGE_PROVIDER_ITEM_SLOT).getItem() instanceof ExperienceStorageItem; diff --git a/src/main/java/de/dafuqs/spectrum/blocks/pastel_network/nodes/PastelNodeBlockEntity.java b/src/main/java/de/dafuqs/spectrum/blocks/pastel_network/nodes/PastelNodeBlockEntity.java index b18f76d04c..b93f6f6289 100644 --- a/src/main/java/de/dafuqs/spectrum/blocks/pastel_network/nodes/PastelNodeBlockEntity.java +++ b/src/main/java/de/dafuqs/spectrum/blocks/pastel_network/nodes/PastelNodeBlockEntity.java @@ -12,6 +12,7 @@ import de.dafuqs.spectrum.networking.*; import de.dafuqs.spectrum.progression.*; import de.dafuqs.spectrum.registries.*; +import it.unimi.dsi.fastutil.objects.*; import net.fabricmc.fabric.api.lookup.v1.block.*; import net.fabricmc.fabric.api.screenhandler.v1.*; import net.fabricmc.fabric.api.transfer.v1.item.*; @@ -28,6 +29,7 @@ import net.minecraft.network.packet.*; import net.minecraft.network.packet.s2c.play.*; import net.minecraft.registry.*; +import net.minecraft.registry.tag.*; import net.minecraft.screen.*; import net.minecraft.server.network.*; import net.minecraft.server.world.*; @@ -46,7 +48,7 @@ import java.util.function.Predicate; @SuppressWarnings("UnstableApiUsage") -public class PastelNodeBlockEntity extends BlockEntity implements FilterConfigurable, ExtendedScreenHandlerFactory, Stampable, PastelUpgradeable { +public class PastelNodeBlockEntity extends BlockEntity implements FilterConfigurable, ExtendedScreenHandlerFactory, Stampable, PastelUpgradeable, TagFilteringInventory { public static final int MAX_FILTER_SLOTS = 25; public static final int SLOTS_PER_ROW = 5; @@ -80,10 +82,14 @@ public class PastelNodeBlockEntity extends BlockEntity implements FilterConfigur float rotationTarget, crystalRotation, lastRotationTarget, heightTarget, crystalHeight, lastHeightTarget, alphaTarget, ringAlpha, lastAlphaTarget; long creationStamp = -1, interpTicks, interpLength = -1, spinTicks; private State state; + + private final Object2BooleanMap> filteredTags; + private boolean allTagsDeny = true; public PastelNodeBlockEntity(BlockPos blockPos, BlockState blockState) { super(SpectrumBlockEntities.PASTEL_NODE, blockPos, blockState); this.filterItems = DefaultedList.ofSize(MAX_FILTER_SLOTS, ItemVariant.blank()); + this.filteredTags = new Object2BooleanArrayMap<>(); this.outerRing = Optional.empty(); this.innerRing = Optional.empty(); this.redstoneRing = Optional.empty(); @@ -253,6 +259,7 @@ public void updateUpgrades() { if (filterSlotRows < oldFilterSlotCount) { for (int i = getDrawnSlots(); i < filterItems.size(); i++) { filterHashset.remove(filterItems.get(i).getItem()); + updateNbtCheckingCount(this.filterItems.get(i), -1); filterItems.set(i, ItemVariant.blank()); } } @@ -359,6 +366,7 @@ public void readNbt(NbtCompound nbt) { this.filterHashset.add(itemVariant.getItem()); this.updateNbtCheckingCount(itemVariant, 1); }); + this.updateTagFilteringItems(); } } @@ -455,6 +463,9 @@ public void updateInClientWorld() { public List getItemFilters() { return this.filterItems; } + public Object2BooleanMap> getFilteredTags() { return this.filteredTags; } + public boolean onlyDenyListTags() { return this.allTagsDeny; } + public void setOnlyDenyListTags(boolean onlyDenyListTags) { this.allTagsDeny = onlyDenyListTags; } @Override public void setFilterItem(int slot, ItemVariant item) { @@ -463,6 +474,7 @@ public void setFilterItem(int slot, ItemVariant item) { if(Collections.frequency(this.filterItems, item) == 1) { this.filterHashset.remove(this.filterItems.get(slot).getItem()); } this.filterItems.set(slot, item); this.filterHashset.add(item.getItem()); + this.updateTagFilteringItems(); } private void updateNbtCheckingCount(ItemVariant itemVariant, int change) { @@ -507,35 +519,9 @@ private boolean filter(ItemVariant variant) { continue filter; } } - - if (!filterStack.hasCustomName() || !filterStack.isIn(SpectrumItemTags.TAG_FILTERING_ITEMS)) { - if (filterStack.getItem() == variant.getItem()) { - return true; - } else { - continue; - } - } - var name = StringUtils.trim(filterStack.getName().getString()); - - // This is to allow nbt filtering without item / tag filtering. - if (StringUtils.equalsAnyIgnoreCase(name, "*", "any", "all", "everything", "c:*", "c:any", "c:all", "c:everything")) - return true; - - var id = Identifier.tryParse(StringUtils.remove(name, '#')); // let's be nice and remove any pound signs for the dumb idiots - if (id == null) - continue; - - var tag = SpectrumCommon.CACHED_ITEM_TAG_MAP.computeIfAbsent(id, tagId -> Registries.ITEM.streamTags() - .filter(t -> t.id().equals(tagId)) - .findFirst() - .orElse(null)); - - if (tag == null) - continue; - - if (variant.getItem().getRegistryEntry().isIn(tag)) - return true; } + + if (this.acceptsItem(variant.getItem())) { return true; } return false; }