diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/OverdriveThatMatters.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/OverdriveThatMatters.kt index bf56c3b7f..0d99c1660 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/OverdriveThatMatters.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/OverdriveThatMatters.kt @@ -52,6 +52,7 @@ import ru.dbotthepony.mc.otm.config.ItemsConfig import ru.dbotthepony.mc.otm.config.MachinesConfig import ru.dbotthepony.mc.otm.config.ServerConfig import ru.dbotthepony.mc.otm.config.ToolsConfig +import ru.dbotthepony.mc.otm.container.ItemFilter import ru.dbotthepony.mc.otm.data.FlywheelMaterials import ru.dbotthepony.mc.otm.data.world.DecimalProvider import ru.dbotthepony.mc.otm.entity.WitheredSkeletonSpawnHandler @@ -141,6 +142,7 @@ object OverdriveThatMatters { AbstractRegistryAction.register(MOD_BUS) IMatterFunction.register(MOD_BUS) + ItemFilter.register(MOD_BUS) MRegistry.initialize(MOD_BUS) MatterManager.initialize(MOD_BUS) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterEntanglerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterEntanglerBlockEntity.kt index 079f70e18..dbdd0e799 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterEntanglerBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterEntanglerBlockEntity.kt @@ -1,11 +1,7 @@ package ru.dbotthepony.mc.otm.block.entity.matter -import com.github.benmanes.caffeine.cache.Cache -import com.github.benmanes.caffeine.cache.Caffeine -import com.github.benmanes.caffeine.cache.Scheduler import com.mojang.serialization.Codec import com.mojang.serialization.codecs.RecordCodecBuilder -import net.minecraft.Util import net.minecraft.core.BlockPos import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Player @@ -39,8 +35,8 @@ import ru.dbotthepony.mc.otm.core.collect.forEach import ru.dbotthepony.mc.otm.core.collect.map import ru.dbotthepony.mc.otm.core.collect.toList import ru.dbotthepony.mc.otm.core.math.Decimal -import ru.dbotthepony.mc.otm.core.util.ItemStackKey -import ru.dbotthepony.mc.otm.core.util.asKey +import ru.dbotthepony.mc.otm.container.ItemStackKey +import ru.dbotthepony.mc.otm.container.asKey import ru.dbotthepony.mc.otm.data.codec.DecimalCodec import ru.dbotthepony.mc.otm.data.codec.minRange import ru.dbotthepony.mc.otm.graph.matter.MatterNode diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageBusBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageBusBlockEntity.kt index 9430419f0..056d2e832 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageBusBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageBusBlockEntity.kt @@ -23,7 +23,7 @@ import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage import ru.dbotthepony.mc.otm.config.MachinesConfig -import ru.dbotthepony.mc.otm.container.ItemFilter +import ru.dbotthepony.mc.otm.container.ItemFilterSet import ru.dbotthepony.mc.otm.core.* import ru.dbotthepony.mc.otm.core.math.RelativeSide import ru.dbotthepony.mc.otm.core.math.isPositive @@ -123,7 +123,7 @@ class StorageBusBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matter }) } - var filter = ItemFilter(MAX_FILTERS) + var filter = ItemFilterSet.EMPTY set(value) { field = value component?.scan() @@ -131,7 +131,7 @@ class StorageBusBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matter } init { - savetablesConfig.codec(::filter, ItemFilter.CODEC, FILTER_KEY, Supplier { ItemFilter(MAX_FILTERS) }) + savetablesConfig.codec(::filter, ItemFilterSet.CODEC, FILTER_KEY, Supplier { ItemFilterSet.EMPTY }) } override fun setLevel(level: Level) { @@ -348,7 +348,7 @@ class StorageBusBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matter } fun scan(slot: Int) { - val current = parent[slot].let { if (it.isEmpty || !filter.match(it)) null else it } + val current = parent[slot].let { if (it.isEmpty || !filter.test(it)) null else it } val last = slot2itemStack[slot] if (current == null && last != null) { @@ -374,7 +374,7 @@ class StorageBusBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matter } override fun insertStack(stack: ItemStorageStack, simulate: Boolean): ItemStorageStack { - if (redstoneControl.isBlockedByRedstone || energy.batteryLevel.isZero || !filter.match(stack.toItemStack()) || !mode.input) + if (redstoneControl.isBlockedByRedstone || energy.batteryLevel.isZero || !filter.test(stack.toItemStack()) || !mode.input) return stack val required = StorageStack.ITEMS.energyPerInsert(stack) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageInterfaces.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageInterfaces.kt index 17aa2e953..d6573d11b 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageInterfaces.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageInterfaces.kt @@ -23,7 +23,7 @@ import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage import ru.dbotthepony.mc.otm.config.EnergyBalanceValues import ru.dbotthepony.mc.otm.config.MachinesConfig -import ru.dbotthepony.mc.otm.container.ItemFilter +import ru.dbotthepony.mc.otm.container.ItemFilterSet import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.core.isNotEmpty import ru.dbotthepony.mc.otm.core.math.RelativeSide @@ -98,7 +98,7 @@ abstract class AbstractStorageImportExport( protected val target = CapabilityCache(RelativeSide.FRONT, Capabilities.ItemHandler.BLOCK) - var filter: ItemFilter = ItemFilter(MAX_FILTERS) + var filter: ItemFilterSet = ItemFilterSet.EMPTY set(value) { if (value != field) { field = value @@ -112,7 +112,7 @@ abstract class AbstractStorageImportExport( } init { - savetablesConfig.codec(::filter, ItemFilter.CODEC, FILTER_KEY, Supplier { ItemFilter(MAX_FILTERS) }) + savetablesConfig.codec(::filter, ItemFilterSet.CODEC, FILTER_KEY, Supplier { ItemFilterSet.EMPTY }) } companion object { @@ -168,7 +168,7 @@ class StorageImporterBlockEntity( } override fun insertItem(slot: Int, stack: ItemStack, simulate: Boolean): ItemStack { - if (redstoneControl.isBlockedByRedstone || !filter.match(stack)) + if (redstoneControl.isBlockedByRedstone || !filter.test(stack)) return stack return acceptItem(stack, simulate) @@ -183,7 +183,7 @@ class StorageImporterBlockEntity( } override fun isItemValid(slot: Int, stack: ItemStack): Boolean { - return filter.match(stack) + return filter.test(stack) } override fun tick() { @@ -205,7 +205,7 @@ class StorageImporterBlockEntity( val extracted = target.extractItem(lastSlot, MAX_MOVE_PER_OPERATION, true) - if (extracted.isEmpty || !filter.match(extracted)) { + if (extracted.isEmpty || !filter.test(extracted)) { lastSlot++ } else { val leftover = acceptItem(extracted, true) @@ -244,7 +244,7 @@ class StorageExporterBlockEntity(blockPos: BlockPos, blockState: BlockState) : } override fun onStackAdded(stack: ItemStorageStack, id: UUID, provider: IStorageProvider) { - if (filter.match(stack.toItemStack())) { + if (filter.test(stack.toItemStack())) { relevantTuples.add(id) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/AbstractPoweredFurnaceBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/AbstractPoweredFurnaceBlockEntity.kt index 8afae2f5c..1145eec92 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/AbstractPoweredFurnaceBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/AbstractPoweredFurnaceBlockEntity.kt @@ -36,8 +36,8 @@ import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer import ru.dbotthepony.mc.otm.core.SimpleCache import ru.dbotthepony.mc.otm.core.immutableList import ru.dbotthepony.mc.otm.core.otmRandom -import ru.dbotthepony.mc.otm.core.util.ItemStackKey -import ru.dbotthepony.mc.otm.core.util.asKey +import ru.dbotthepony.mc.otm.container.ItemStackKey +import ru.dbotthepony.mc.otm.container.asKey import ru.dbotthepony.mc.otm.menu.tech.PoweredFurnaceMenu import ru.dbotthepony.mc.otm.recipe.MatteryCookingRecipe import ru.dbotthepony.mc.otm.recipe.MicrowaveRecipe diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/PlatePressBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/PlatePressBlockEntity.kt index 16ec5cf96..a415dfc26 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/PlatePressBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/PlatePressBlockEntity.kt @@ -23,12 +23,9 @@ import ru.dbotthepony.mc.otm.container.slotted.AutomationFilters import ru.dbotthepony.mc.otm.container.slotted.FilteredContainerSlot import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer import ru.dbotthepony.mc.otm.core.SimpleCache -import ru.dbotthepony.mc.otm.core.collect.any -import ru.dbotthepony.mc.otm.core.collect.filter -import ru.dbotthepony.mc.otm.core.collect.maybe import ru.dbotthepony.mc.otm.core.otmRandom -import ru.dbotthepony.mc.otm.core.util.ItemStackKey -import ru.dbotthepony.mc.otm.core.util.asKey +import ru.dbotthepony.mc.otm.container.ItemStackKey +import ru.dbotthepony.mc.otm.container.asKey import ru.dbotthepony.mc.otm.menu.tech.PlatePressMenu import ru.dbotthepony.mc.otm.registry.game.MBlockEntities import ru.dbotthepony.mc.otm.registry.game.MRecipes diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/Ext.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/Ext.kt index 489398d4e..644e715c1 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/Ext.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/Ext.kt @@ -149,10 +149,10 @@ fun Player.items(includeCosmetics: Boolean = true): Iterator { val matteryPlayer = matteryPlayer val iterators = ArrayList>() - iterators.add(matteryPlayer.wrappedInventory.slotIterator().filter { !it.isForbiddenForAutomation }.map { it.item }) + iterators.add(matteryPlayer.wrappedInventory.slotIterator().filter { !it.filter.denyAll }.map { it.item }) if (matteryPlayer.hasExopack) { - iterators.add(matteryPlayer.exopackContainer.slotIterator().filter { !it.isForbiddenForAutomation }.map { it.item }) + iterators.add(matteryPlayer.exopackContainer.slotIterator().filter { !it.filter.denyAll }.map { it.item }) iterators.add(matteryPlayer.exopackEnergy.parent.iterator()) iterators.add(matteryPlayer.exopackChargeSlots.iterator()) } @@ -185,10 +185,8 @@ fun Player.awareItemsStream(includeCosmetics: Boolean = false): Stream>() streams.add(inventory.awareStream()) - matteryPlayer?.let { - if (it.hasExopack) { - streams.add(it.exopackContainer.awareStream()) - } + if (matteryPlayer.hasExopack) { + streams.add(matteryPlayer.exopackContainer.awareStream()) } if (isCuriosLoaded) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/FilterSlotPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/FilterSlotPanel.kt index b3ebe2cab..698ce1a91 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/FilterSlotPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/FilterSlotPanel.kt @@ -4,22 +4,36 @@ import net.minecraft.world.item.ItemStack import ru.dbotthepony.kommons.util.Delegate import ru.dbotthepony.mc.otm.client.screen.MatteryScreen import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel +import ru.dbotthepony.mc.otm.container.ItemFilter -open class FilterSlotPanel> @JvmOverloads constructor( +open class FilterSlotPanel>( screen: S, parent: EditablePanel<*>?, - val slot: Delegate, + val slot: Delegate, x: Float = 0f, y: Float = 0f, width: Float = SIZE, height: Float = SIZE ) : AbstractSlotPanel(screen, parent, x, y, width, height) { + private var lastFilteredItemDisplayUpdate = System.nanoTime() + private var filteredItemDisplayIndex = 0 + override val itemStack: ItemStack get() { - return slot.get() + val items = slot.get().displayItems + + if (items.isEmpty()) + return ItemStack.EMPTY + + if (System.nanoTime() - lastFilteredItemDisplayUpdate >= 1_000_000_000L || filteredItemDisplayIndex !in items.indices) { + lastFilteredItemDisplayUpdate = System.nanoTime() + filteredItemDisplayIndex = random.nextInt(items.size) + } + + return items.asList()[filteredItemDisplayIndex].asItemStack() } override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean { - slot.accept(screen.menu.carried) + slot.accept(ItemFilter.item(screen.menu.carried)) return true } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/SlotPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/SlotPanel.kt index 1deb00732..3ebc0be4a 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/SlotPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/SlotPanel.kt @@ -20,9 +20,12 @@ import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel import ru.dbotthepony.mc.otm.compat.itemborders.isItemBordersLoaded import ru.dbotthepony.mc.otm.compat.itemborders.renderSlotBorder import ru.dbotthepony.mc.otm.container.IFilteredContainerSlot +import ru.dbotthepony.mc.otm.container.ItemFilter +import ru.dbotthepony.mc.otm.container.ItemStackKey import ru.dbotthepony.mc.otm.container.util.containerSlotOrNull import ru.dbotthepony.mc.otm.core.TextComponent import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.isNotEmpty import ru.dbotthepony.mc.otm.menu.UserFilteredMenuSlot import javax.annotation.Nonnull import kotlin.math.roundToInt @@ -63,6 +66,24 @@ open class SlotPanel, out T : Slot>( protected open fun renderBackgroundBeforeFilter(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) {} + private var lastFilteredItemDisplayUpdate = System.nanoTime() + private var filteredItemDisplayIndex = 0 + + private fun selectRandomItemFromFilter(filter: ItemFilter): ItemStack { + val items = filter.displayItems + + if (items.isEmpty()) { + return ItemStack.EMPTY + } else { + if (System.nanoTime() - lastFilteredItemDisplayUpdate >= 1_000_000_000L || filteredItemDisplayIndex !in items.indices) { + lastFilteredItemDisplayUpdate = System.nanoTime() + filteredItemDisplayIndex = random.nextInt(items.size) + } + + return items.asList()[filteredItemDisplayIndex].asItemStack() + } + } + override fun renderSlotBackground(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { super.renderSlotBackground(graphics, mouseX, mouseY, partialTick) @@ -71,16 +92,16 @@ open class SlotPanel, out T : Slot>( if (containerSlot is IFilteredContainerSlot) { renderBackgroundBeforeFilter(graphics, mouseX, mouseY, partialTick) - if (containerSlot.filter !== null) { - if (containerSlot.filter !== Items.AIR) { - val itemStack = ItemStack(containerSlot.filter!!, 1) + if (containerSlot.filter.denyAll) { + graphics.renderRect(0f, 0f, width, height, color = SLOT_BLOCK_COLOR) + } else if (!containerSlot.filter.allowAll) { + val itemStack = selectRandomItemFromFilter(containerSlot.filter) + if (itemStack.isNotEmpty) { screen.renderItemStack(graphics, itemStack, null) clearDepth(graphics) graphics.renderRect(0f, 0f, width, height, color = SLOT_FILTER_COLOR) - } else { - graphics.renderRect(0f, 0f, width, height, color = SLOT_BLOCK_COLOR) } } } @@ -154,26 +175,37 @@ open class SlotPanel, out T : Slot>( } } - override fun innerRenderTooltips(@Nonnull graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float): Boolean { + override fun innerRenderTooltips(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float): Boolean { val slot = slot.container.containerSlotOrNull(slot.containerSlot) as? IFilteredContainerSlot - if (isHovered && slot?.filter != null && slot.filter !== Items.AIR && itemStack.isEmpty) { - val itemstack = ItemStack(slot.filter!!, 1) + if (isHovered && slot?.filter != null && slot.filter.hasRules && itemStack.isEmpty) { + val itemstack = selectRandomItemFromFilter(slot.filter) - graphics.renderComponentTooltip( - IClientItemExtensions.of(itemstack).getFont(itemstack, IClientItemExtensions.FontContext.TOOLTIP) ?: font, - getItemStackTooltip(itemstack).toMutableList().also { + val text: List + + if (itemstack.isEmpty) { + text = listOf( + TranslatableComponent("otm.gui.slot_filter.filtered").withStyle(ChatFormatting.GRAY), + TranslatableComponent("otm.gui.slot_filter.hint").withStyle(ChatFormatting.GRAY) + ) + } else { + text = getItemStackTooltip(itemstack).toMutableList().also { it.add(0, TranslatableComponent("otm.gui.slot_filter.filtered").withStyle(ChatFormatting.GRAY)) it.add(1, TranslatableComponent("otm.gui.slot_filter.hint").withStyle(ChatFormatting.GRAY)) it.add(2, TextComponent("")) - }, + } + } + + graphics.renderComponentTooltip( + IClientItemExtensions.of(itemstack).getFont(itemstack, IClientItemExtensions.FontContext.TOOLTIP) ?: font, + text, mouseX.toInt(), mouseY.toInt(), itemstack ) return true - } else if (isHovered && slot?.filter === Items.AIR && itemStack.isEmpty) { + } else if (isHovered && slot?.filter?.denyAll == true && itemStack.isEmpty) { graphics.renderComponentTooltip( font, ArrayList().also { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/UserFilteredSlotPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/UserFilteredSlotPanel.kt index 2d6a269a7..93edb8608 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/UserFilteredSlotPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/UserFilteredSlotPanel.kt @@ -7,6 +7,7 @@ import ru.dbotthepony.mc.otm.client.playGuiClickSound import ru.dbotthepony.mc.otm.client.screen.MatteryScreen import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel import ru.dbotthepony.mc.otm.container.IFilteredContainerSlot +import ru.dbotthepony.mc.otm.container.ItemFilter import ru.dbotthepony.mc.otm.container.util.containerSlot import ru.dbotthepony.mc.otm.menu.UserFilteredMenuSlot @@ -20,20 +21,18 @@ open class UserFilteredSlotPanel, out T : UserFilteredM height: Float = SIZE, ) : SlotPanel(screen, parent, slot, x, y, width, height) { override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean { - if (slot.filterInput == null) - return super.mouseClickedInner(x, y, button) - + val filterInput = slot.filterInput ?: return super.mouseClickedInner(x, y, button) val containerSlot = slot.containerSlot() as IFilteredContainerSlot if (button == InputConstants.MOUSE_BUTTON_LEFT && minecraft.window.isCtrlDown) { - if (containerSlot.filter === null) { + if (containerSlot.filter.allowAll) { if (screen.menu.carried.isEmpty) { - slot.filterInput!!.accept(slot.item.item) + filterInput.accept(ItemFilter.item(slot.item.item)) } else { - slot.filterInput!!.accept(screen.menu.carried.item) + filterInput.accept(ItemFilter.item(screen.menu.carried.item)) } } else { - slot.filterInput!!.accept(null) + filterInput.accept(ItemFilter.EMPTY) } playGuiClickSound() diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/storage/DriveViewerScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/storage/DriveViewerScreen.kt index 96c9022ee..5a6a5ffa9 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/storage/DriveViewerScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/storage/DriveViewerScreen.kt @@ -66,14 +66,12 @@ class DriveViewerScreen(menu: DriveViewerMenu, inventory: Inventory, title: Comp settings.add(filterGrid) for (i in 0 until PortableCondensationDriveItem.MAX_FILTERS) { - FilterSlotPanel(this, filterGrid, menu.driveFilterSlots[i], 0f, 0f) + FilterSlotPanel(this, filterGrid, menu.driveFilter.slots[i], 0f, 0f) } settings.add(EditablePanel(this, frame, width = 90f).also { it.dock = Dock.LEFT - BooleanButtonPanel.Checkbox(this, it, menu.isWhitelist, TranslatableComponent("otm.gui.filter.is_whitelist")).also { it.dockTop = 20f; it.dock = Dock.TOP } - BooleanButtonPanel.Checkbox(this, it, menu.matchTag, TranslatableComponent("otm.gui.filter.match_tag")).also { it.dockTop = 4f; it.dock = Dock.TOP } - BooleanButtonPanel.Checkbox(this, it, menu.matchComponents, TranslatableComponent("otm.gui.filter.match_nbt")).also { it.dockTop = 4f; it.dock = Dock.TOP } + BooleanButtonPanel.Checkbox(this, it, menu.driveFilter.isWhitelist, TranslatableComponent("otm.gui.filter.is_whitelist")).also { it.dockTop = 20f; it.dock = Dock.TOP } }) frame.CustomTab(view, activeIcon = ItemStackIcon(ItemStack(MItems.PORTABLE_CONDENSATION_DRIVE))) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/storage/StorageImporterExporterScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/storage/StorageImporterExporterScreen.kt index a85e84860..4b1669648 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/storage/StorageImporterExporterScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/storage/StorageImporterExporterScreen.kt @@ -34,18 +34,6 @@ class StorageImporterExporterScreen(menu: StorageImporterExporterMenu, inventory it.childrenOrder = -1 } - BooleanButtonPanel.Checkbox(this, right, menu.filter.matchComponents, TranslatableComponent("otm.gui.filter.match_nbt")).also { - it.dock = Dock.BOTTOM - it.dockTop = 2f - it.childrenOrder = -2 - } - - BooleanButtonPanel.Checkbox(this, right, menu.filter.matchTag, TranslatableComponent("otm.gui.filter.match_tag")).also { - it.dock = Dock.BOTTOM - it.dockTop = 2f - it.childrenOrder = -3 - } - makeDeviceControls(this, frame, redstoneConfig = menu.redstoneConfig, energyConfig = menu.energyConfig) return frame diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerHelpers.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerHelpers.kt index cb61feae0..6cee3f401 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerHelpers.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerHelpers.kt @@ -241,9 +241,9 @@ fun Container.sortWithIndices(sortedSlots: IntCollection) { if (slot is IFilteredContainerSlot) { condition = slot.isNotEmpty && - !slot.isForbiddenForAutomation && + !slot.filter.denyAll && slot.item.count <= slot.maxStackSize(slot.item) && - (!slot.hasFilter || slot.filter != slot.item.item || slot.maxStackSize(slot.item) > 1) + (slot.filter.allowAll || !slot.filter.test(slot.item) || slot.maxStackSize(slot.item) > 1) } else { condition = slot.isNotEmpty && slot.item.count <= slot.maxStackSize(slot.item) } @@ -268,7 +268,7 @@ fun Container.computeSortedIndices(comparator: Comparator = ItemStack val slots = slotIterator() .withIndex() - .filter { (_, it) -> (it !is IFilteredContainerSlot || !it.isForbiddenForAutomation) && it.maxStackSize(it.item) >= it.item.count } + .filter { (_, it) -> (it !is IFilteredContainerSlot || !it.filter.denyAll) && it.maxStackSize(it.item) >= it.item.count } .toList() if (slots.isEmpty()) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/IEnhancedContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/IEnhancedContainer.kt index af2b978fe..c201a4bb8 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/IEnhancedContainer.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/IEnhancedContainer.kt @@ -282,9 +282,9 @@ interface IEnhancedContainer : Container, RecipeInput, I val condition: Boolean if (slot is IFilteredContainerSlot) { - condition = (ignoreFilters || !slot.isForbiddenForAutomation) && + condition = (ignoreFilters || !slot.filter.denyAll) && ItemStack.isSameItemSameComponents(slot.item, stack) && - (ignoreFilters || !filterPass && !slot.hasFilter || filterPass && slot.hasFilter && slot.testSlotFilter(stack)) + (ignoreFilters || !filterPass && slot.filter.allowAll || filterPass && !slot.filter.allowAll && slot.filter.test(stack)) } else { condition = (ignoreFilters || !filterPass) && ItemStack.isSameItemSameComponents(slot.item, stack) } @@ -318,8 +318,8 @@ interface IEnhancedContainer : Container, RecipeInput, I val condition: Boolean if (slot is IFilteredContainerSlot) { - condition = (ignoreFilters || !slot.isForbiddenForAutomation) && - (ignoreFilters || !filterPass && !slot.hasFilter || filterPass && slot.hasFilter && slot.testSlotFilter(stack)) + condition = (ignoreFilters || !slot.filter.denyAll) && + (ignoreFilters || !filterPass && slot.filter.allowAll || filterPass && !slot.filter.allowAll && slot.filter.test(stack)) } else { condition = ignoreFilters || !filterPass } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/IFilteredAutomatedContainerSlot.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/IFilteredAutomatedContainerSlot.kt index a94f6ba05..4ee9144a0 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/IFilteredAutomatedContainerSlot.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/IFilteredAutomatedContainerSlot.kt @@ -5,10 +5,10 @@ import net.minecraft.world.item.Items interface IFilteredAutomatedContainerSlot : IFilteredContainerSlot, IAutomatedContainerSlot { override fun canAutomationPlaceItem(itemStack: ItemStack): Boolean { - return super.canAutomationPlaceItem(itemStack) && testSlotFilter(itemStack) + return super.canAutomationPlaceItem(itemStack) && filter.test(itemStack) } override fun canAutomationTakeItem(desired: Int): Boolean { - return super.canAutomationTakeItem(desired) && (filter == null || filter !== Items.AIR) + return super.canAutomationTakeItem(desired) && !filter.denyAll } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/IFilteredContainerSlot.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/IFilteredContainerSlot.kt index 11bb4c44a..6562192b0 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/IFilteredContainerSlot.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/IFilteredContainerSlot.kt @@ -1,28 +1,5 @@ package ru.dbotthepony.mc.otm.container -import net.minecraft.world.item.Item -import net.minecraft.world.item.ItemStack -import net.minecraft.world.item.Items - interface IFilteredContainerSlot : IContainerSlot { - var filter: Item? - - val isForbiddenForAutomation: Boolean get() { - return filter === Items.AIR - } - - val hasFilter: Boolean - get() = filter != null - - fun testSlotFilter(itemStack: ItemStack): Boolean { - return testSlotFilter(itemStack.item) - } - - fun testSlotFilter(item: Item): Boolean { - if (filter == null) { - return true - } else { - return filter === item - } - } + var filter: ItemFilter } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemFilter.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemFilter.kt index 4f0f53114..762b65a94 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemFilter.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemFilter.kt @@ -1,124 +1,205 @@ package ru.dbotthepony.mc.otm.container +import com.google.common.collect.ImmutableSet +import com.mojang.datafixers.util.Either import com.mojang.serialization.Codec -import com.mojang.serialization.codecs.RecordCodecBuilder -import it.unimi.dsi.fastutil.objects.ObjectArrayList +import com.mojang.serialization.DataResult +import com.mojang.serialization.MapCodec +import net.minecraft.core.component.DataComponentPatch +import net.minecraft.core.registries.BuiltInRegistries +import net.minecraft.core.registries.Registries +import net.minecraft.resources.ResourceLocation import net.minecraft.tags.TagKey -import net.minecraft.world.item.Item import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.Items +import net.neoforged.bus.api.IEventBus +import ru.dbotthepony.mc.otm.core.isNotEmpty +import ru.dbotthepony.mc.otm.registry.MBuiltInRegistries +import ru.dbotthepony.mc.otm.registry.MDeferredRegister +import ru.dbotthepony.mc.otm.registry.MRegistries +import java.util.function.Predicate +import kotlin.jvm.optionals.getOrElse -class ItemFilter private constructor(private val filter: Array, val isWhitelist: Boolean, val matchTag: Boolean, val matchComponents: Boolean) { - constructor(size: Int, isWhitelist: Boolean = false, matchTag: Boolean = false, matchComponents: Boolean = false) : this(Array(size) { ItemStack.EMPTY }, isWhitelist, matchTag, matchComponents) - constructor(list: List, isWhitelist: Boolean = false, matchTag: Boolean = false, matchComponents: Boolean = false) : this(list.toTypedArray(), isWhitelist, matchTag, matchComponents) - - override fun equals(other: Any?): Boolean { - return this === other || other is ItemFilter && - this.filter.contentEquals(other.filter) && - this.isWhitelist == other.isWhitelist && - this.matchTag == other.matchTag && - this.matchComponents == other.matchComponents +interface ItemFilter : Predicate { + interface Type { + val codec: MapCodec } - override fun hashCode(): Int { - return filter.contentHashCode() + val type: Type<*> + + /** + * Whenever [test] will return `true` no matter the argument, + * effectively telling that this filter is "allow all" / "no filter specified" + * + * Can be treated as (but is not equal to) "isEmpty" + */ + val allowAll: Boolean + get() = false + + /** + * Whenever [test] will return `false` no matter the argument, + * effectively telling that this filter is "deny all" / "forbidden for automation" + */ + val denyAll: Boolean + get() = false + + /** + * Whenever this filter has meaningful rules behind it, e.g. [test] will return either `true` or `false`, + * depending on value passed. + * + * In other words, returns whenever [denyAll] and [allowAll] are both `false`. + */ + val hasRules: Boolean + get() = !denyAll && !allowAll + + val depth: Int + get() = 1 + + val displayItems: ImmutableSet + get() = ImmutableSet.of() + + private data class Item(val item: net.minecraft.world.item.Item) : ItemFilter { + override val type: Type<*> + get() = Companion + + override val denyAll: Boolean + get() = item === Items.AIR + + override fun test(t: ItemStack): Boolean { + return t.isNotEmpty && t.item === item + } + + override val displayItems: ImmutableSet + get() = ImmutableSet.of(ItemStackKey(item)) + + companion object : Type { + override val codec: MapCodec by lazy { + BuiltInRegistries.ITEM.byNameCodec().xmap(::Item, Item::item).fieldOf("item") + } + } } - val size: Int - get() = filter.size + private data class Tag(val tag: TagKey) : ItemFilter { + override val type: Type<*> + get() = Companion - fun set(index: Int, value: ItemStack): ItemFilter { - if (ItemStack.isSameItemSameComponents(filter[index], value) || !value.isEmpty && filter.any { ItemStack.isSameItemSameComponents(it, value) }) - return this + override fun test(t: ItemStack): Boolean { + return t.`is`(tag) + } - return copy(filter.copyOf().also { it[index] = value }) + // TODO: can not be "lazy" cached because this will break with /reload command + override val displayItems: ImmutableSet get() { + return BuiltInRegistries.ITEM + .getTag(tag) + .map { it.stream().map { ItemStackKey(it.value()) }.collect(ImmutableSet.toImmutableSet()) } + .orElseGet { ImmutableSet.of() } + } + + companion object : Type { + override val codec: MapCodec by lazy { + TagKey.codec(Registries.ITEM).xmap(::Tag, Tag::tag).fieldOf("tag") + } + } } - operator fun get(index: Int): ItemStack { - return filter[index] - } + private object DenyAll : ItemFilter, Type { + override val type: Type<*> + get() = this - private fun copy( - filter: Array = this.filter, - isWhitelist: Boolean = this.isWhitelist, - matchTag: Boolean = this.matchTag, - matchComponents: Boolean = this.matchComponents, - ) = ItemFilter(filter, isWhitelist, matchTag, matchComponents) + override val codec: MapCodec = MapCodec.unit(this) - fun isWhitelist(flag: Boolean): ItemFilter { - if (flag == isWhitelist) - return this - else - return copy(isWhitelist = flag) - } + override val denyAll: Boolean + get() = true - fun matchTag(flag: Boolean): ItemFilter { - if (flag == matchTag) - return this - else - return copy(matchTag = flag) - } - - fun matchComponents(flag: Boolean): ItemFilter { - if (flag == matchComponents) - return this - else - return copy(matchComponents = flag) - } - - fun match(value: ItemStack): Boolean { - if (value.isEmpty) { + override fun test(t: ItemStack): Boolean { return false } - - if (filter.isEmpty()) { - return !isWhitelist - } - - for (item in filter) { - var matches = item.`is`(value.item) - - if (matches && matchTag) { - matches = false - - val thisTags = item.tags - val stackTags = HashSet>() - - for (tag in value.tags) { - stackTags.add(tag) - } - - for (tag1 in thisTags) { - if (stackTags.contains(tag1)) { - matches = true - break - } - } - } - - if (matches && matchComponents) { - matches = item.components == value.components - } - - if (matches) { - return isWhitelist - } - } - - return !isWhitelist } - companion object { - val EMPTY = ItemFilter(0) + companion object : ItemFilter, Type { + const val MAX_DEPTH = 16 + + private fun roll(input: ItemFilter): Either { + if (input is Item) { + return Either.left(input) + } else if (input is ItemStackKey && input.components == DataComponentPatch.EMPTY) { + return Either.left(Item(input.item)) + } else { + return Either.right(input) + } + } val CODEC: Codec by lazy { - RecordCodecBuilder.create { - it.group( - Codec.list(ItemStack.OPTIONAL_CODEC, 0, 40).fieldOf("filter").forGetter { ObjectArrayList.wrap(it.filter) }, - Codec.BOOL.optionalFieldOf("isWhitelist", false).forGetter { it.isWhitelist }, - Codec.BOOL.optionalFieldOf("matchTag", false).forGetter { it.matchTag }, - Codec.BOOL.optionalFieldOf("matchComponents", false).forGetter { it.matchComponents }, - ).apply(it, ::ItemFilter) - } + val codecA = BuiltInRegistries.ITEM + .byNameCodec() + .xmap(::Item, Item::item) + + val codecB = MBuiltInRegistries.ITEM_FILTER + .byNameCodec() + .dispatch(ItemFilter::type, { it.codec }) + + Codec.either(codecA, codecB) + .xmap({ it.right().getOrElse { it.left().get() } }, ::roll) + .validate { + if (it.depth <= MAX_DEPTH) { + return@validate DataResult.success(it) + } else { + return@validate DataResult.error { "Too deep item filter, max depth of $MAX_DEPTH is allowed (depth: ${it.depth})" } + } + } + } + + val EMPTY: ItemFilter + get() = this + + val DENY_ALL: ItemFilter + get() = DenyAll + + private val registrar = MDeferredRegister(MRegistries.ITEM_FILTER) + + init { + registrar.register("item") { Item.Companion } + registrar.register("item_stack") { ItemStackKey.Companion } + registrar.register("tag") { Tag.Companion } + registrar.register("set") { ItemFilterSet.Companion } + registrar.register("empty") { this } + registrar.register("deny_all") { DenyAll } + } + + internal fun register(bus: IEventBus) { + registrar.register(bus) + } + + override val type: Type<*> + get() = this + + override fun test(t: ItemStack): Boolean { + return true + } + + override val codec: MapCodec = MapCodec.unit(this) + + override val allowAll: Boolean + get() = true + + @JvmStatic + fun item(item: net.minecraft.world.item.Item): ItemFilter { + return Item(item) + } + + @JvmStatic + fun item(item: ItemStack): ItemFilter { + return Item(item.item) + } + + @JvmStatic + fun itemAndComponents(item: ItemStack): ItemFilter { + return ItemStackKey(item) + } + + @JvmStatic + fun tag(tag: TagKey): ItemFilter { + return Tag(tag) } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemFilterSet.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemFilterSet.kt new file mode 100644 index 000000000..8319020b8 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemFilterSet.kt @@ -0,0 +1,130 @@ +package ru.dbotthepony.mc.otm.container + +import com.google.common.collect.ImmutableSet +import com.mojang.serialization.Codec +import com.mojang.serialization.MapCodec +import com.mojang.serialization.codecs.RecordCodecBuilder +import it.unimi.dsi.fastutil.objects.ObjectArrayList +import net.minecraft.world.item.ItemStack + +data class ItemFilterSet(val filter: ImmutableSet, val isWhitelist: Boolean = false) : ItemFilter { + constructor(list: Collection, isWhitelist: Boolean = false) : this(ImmutableSet.copyOf(list), isWhitelist) + + val size: Int + get() = filter.size + + override val allowAll: Boolean by lazy(LazyThreadSafetyMode.PUBLICATION) { + filter.isEmpty() && !isWhitelist || filter.isNotEmpty() && isWhitelist && filter.any { it.allowAll } + } + + override val denyAll: Boolean by lazy(LazyThreadSafetyMode.PUBLICATION) { + filter.isEmpty() && isWhitelist || filter.isNotEmpty() && !isWhitelist && filter.any { it.denyAll } + } + + fun replace(index: Int, value: ItemFilter): ItemFilterSet { + if (index !in filter.indices) + throw IndexOutOfBoundsException("No such filter at index $index") + else if (value in filter) + return this + + val values = ObjectArrayList(filter) + values[index] = value + return copy(filter = ImmutableSet.copyOf(values)) + } + + fun addOrReplace(index: Int, value: ItemFilter): ItemFilterSet { + if (index !in filter.indices) + return add(value) + else + return replace(index, value) + } + + fun add(value: ItemFilter): ItemFilterSet { + if (value in filter) + return this + + val values = ObjectArrayList(filter) + values.add(value) + return copy(filter = ImmutableSet.copyOf(values)) + } + + fun removeAt(index: Int): ItemFilterSet { + if (index !in filter.indices) + throw IndexOutOfBoundsException("No such filter at index $index") + + if (filter.size == 1) + return copy(filter = ImmutableSet.of()) + + val values = ObjectArrayList(filter) + values.removeAt(index) + return copy(filter = ImmutableSet.copyOf(values)) + } + + fun indexOf(value: ItemFilter): Int { + return filter.asList().indexOf(value) + } + + operator fun get(index: Int): ItemFilter { + return filter.asList()[index] + } + + fun clear(): ItemFilterSet { + if (filter.isEmpty()) + return this + + return copy(filter = ImmutableSet.of()) + } + + fun isWhitelist(flag: Boolean): ItemFilterSet { + if (flag == isWhitelist) + return this + else + return copy(isWhitelist = flag) + } + + override fun test(value: ItemStack): Boolean { + return if (denyAll || value.isEmpty) + false + else if (allowAll) + true + else if (filter.any { it.test(value) }) + isWhitelist + else + !isWhitelist + } + + override val type: ItemFilter.Type<*> + get() = Companion + + override val depth: Int by lazy { + if (filter.isNotEmpty()) + return@lazy 1 + filter.maxOf { it.depth } + + return@lazy 1 + } + + // TODO: can not be "lazy" cached because that will break with /reload command + override val displayItems: ImmutableSet get() { + val sub = filter.map { it.displayItems } + val results = ArrayList(sub.sumOf { it.size }) + sub.forEach { results.addAll(it) } + return ImmutableSet.copyOf(results) + } + + companion object : ItemFilter.Type { + override val codec: MapCodec by lazy { + RecordCodecBuilder.mapCodec { + it.group( + Codec.list(ItemFilter.CODEC, 0, 40).fieldOf("filter").forGetter { ObjectArrayList(it.filter) }, + Codec.BOOL.optionalFieldOf("isWhitelist", false).forGetter { it.isWhitelist }, + ).apply(it, ::ItemFilterSet) + } + } + + val EMPTY = ItemFilterSet(ImmutableSet.of()) + + val CODEC: Codec by lazy { + codec.codec() + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ItemStackKey.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemStackKey.kt similarity index 59% rename from src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ItemStackKey.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemStackKey.kt index 0145c1988..9fc344f59 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ItemStackKey.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemStackKey.kt @@ -1,15 +1,18 @@ -package ru.dbotthepony.mc.otm.core.util +package ru.dbotthepony.mc.otm.container +import com.google.common.collect.ImmutableSet +import com.mojang.serialization.Codec +import com.mojang.serialization.MapCodec +import com.mojang.serialization.codecs.RecordCodecBuilder import it.unimi.dsi.fastutil.HashCommon -import net.minecraft.core.component.DataComponentMap import net.minecraft.core.component.DataComponentPatch -import net.minecraft.core.component.PatchedDataComponentMap import net.minecraft.core.registries.BuiltInRegistries import net.minecraft.world.item.Item import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.Items import ru.dbotthepony.mc.otm.core.getHolder -class ItemStackKey(val item: Item, val components: DataComponentPatch) { +class ItemStackKey(val item: Item, val components: DataComponentPatch) : ItemFilter { // make copy of original itemstack because there is no copy() method on DataComponentMap, which is returned by ItemStack#getComponents constructor(itemStack: ItemStack) : this(itemStack.item, itemStack.copy().componentsPatch) constructor(item: Item) : this(item, DataComponentPatch.EMPTY) @@ -37,6 +40,33 @@ class ItemStackKey(val item: Item, val components: DataComponentPatch) { override fun toString(): String { return "ItemStackKey[$item, $components]" } + + override fun test(t: ItemStack): Boolean { + return t.item === item && t.componentsPatch == components + } + + override val type: ItemFilter.Type<*> + get() = Companion + + override val displayItems: ImmutableSet = ImmutableSet.of(this) + + companion object : ItemFilter.Type { + override val codec: MapCodec + get() = MAP_CODEC + + val MAP_CODEC: MapCodec by lazy { + RecordCodecBuilder.mapCodec { + it.group( + BuiltInRegistries.ITEM.byNameCodec().fieldOf("item").forGetter(ItemStackKey::item), + DataComponentPatch.CODEC.fieldOf("components").forGetter(ItemStackKey::components) + ).apply(it, ::ItemStackKey) + } + } + + val CODEC: Codec by lazy { + MAP_CODEC.codec() + } + } } fun ItemStack.asKey(): ItemStackKey { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/slotted/FilteredContainerSlot.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/slotted/FilteredContainerSlot.kt index 909534c01..d78c871bf 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/slotted/FilteredContainerSlot.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/slotted/FilteredContainerSlot.kt @@ -3,10 +3,13 @@ package ru.dbotthepony.mc.otm.container.slotted import net.minecraft.core.HolderLookup import net.minecraft.core.registries.BuiltInRegistries import net.minecraft.nbt.CompoundTag +import net.minecraft.nbt.NbtOps import net.minecraft.resources.ResourceLocation import net.minecraft.world.item.Item import net.minecraft.world.item.ItemStack +import org.apache.logging.log4j.LogManager import ru.dbotthepony.mc.otm.container.IFilteredAutomatedContainerSlot +import ru.dbotthepony.mc.otm.container.ItemFilter import ru.dbotthepony.mc.otm.core.nbt.set import ru.dbotthepony.mc.otm.core.registryName import java.util.Collections @@ -15,7 +18,7 @@ open class FilteredContainerSlot( container: SlottedContainer, slot: Int ) : ContainerSlot(container, slot), IFilteredAutomatedContainerSlot { - override var filter: Item? = null + override var filter: ItemFilter = ItemFilter.EMPTY set(value) { if (field !== value) { field = value @@ -25,21 +28,25 @@ open class FilteredContainerSlot( override fun clear() { super.clear() - filter = null + filter = ItemFilter.EMPTY } override fun serializeNBT(provider: HolderLookup.Provider): CompoundTag { return super.serializeNBT(provider).also { - if (filter != null) - it["filter"] = filter!!.registryName!!.toString() + it["filter"] = ItemFilter.CODEC.encodeStart(provider.createSerializationContext(NbtOps.INSTANCE), filter) + .getOrThrow { RuntimeException("Failed to serialize item filter: $it") } } } override fun deserializeNBT(provider: HolderLookup.Provider, nbt: CompoundTag) { super.deserializeNBT(provider, nbt) + filter = ItemFilter.EMPTY + if ("filter" in nbt) { - filter = BuiltInRegistries.ITEM.get(ResourceLocation.parse(nbt.getString("filter"))) + ItemFilter.CODEC.decode(provider.createSerializationContext(NbtOps.INSTANCE), nbt) + .ifError { LOGGER.error("Unable to deserialize item filter: ${it.message()}") } + .resultOrPartial().map { it.first }.ifPresent { filter = it } } } @@ -87,4 +94,8 @@ open class FilteredContainerSlot( return Instance(container, index) } } + + companion object { + private val LOGGER = LogManager.getLogger() + } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/slotted/SlottedContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/slotted/SlottedContainer.kt index 31a8b5795..50fe19366 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/slotted/SlottedContainer.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/slotted/SlottedContainer.kt @@ -22,6 +22,7 @@ import ru.dbotthepony.mc.otm.container.EnhancedContainer import ru.dbotthepony.mc.otm.container.IAutomatedContainer import ru.dbotthepony.mc.otm.container.IAutomatedContainerSlot import ru.dbotthepony.mc.otm.container.IFilteredContainerSlot +import ru.dbotthepony.mc.otm.container.ItemFilter import ru.dbotthepony.mc.otm.container.balance import ru.dbotthepony.mc.otm.core.isNotEmpty import ru.dbotthepony.mc.otm.core.nbt.set @@ -293,7 +294,7 @@ class SlottedContainer( val getSlot = slots[slot] if (getSlot is IFilteredContainerSlot) { - getSlot.filter = filter + getSlot.filter = ItemFilter.item(filter) } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/PortableCondensationDriveItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/PortableCondensationDriveItem.kt index c23e65dee..26250d8a3 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/PortableCondensationDriveItem.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/PortableCondensationDriveItem.kt @@ -13,7 +13,7 @@ import net.neoforged.neoforge.event.entity.player.ItemEntityPickupEvent import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.capability.drive.DrivePool import ru.dbotthepony.mc.otm.capability.drive.ItemMatteryDrive -import ru.dbotthepony.mc.otm.container.ItemFilter +import ru.dbotthepony.mc.otm.container.ItemFilterSet import ru.dbotthepony.mc.otm.core.TextComponent import ru.dbotthepony.mc.otm.isServerThread import ru.dbotthepony.mc.otm.registry.CapabilitiesRegisterListener @@ -55,18 +55,18 @@ class PortableCondensationDriveItem(capacity: Int) : Item(Properties().stacksTo( }, this) } - fun getFilterSettings(item: ItemStack): ItemFilter { + fun getFilterSettings(item: ItemStack): ItemFilterSet { return item.getOrDefault(MDataComponentTypes.ITEM_FILTER, EMPTY_FILTER) } - fun setFilterSettings(item: ItemStack, filter: ItemFilter) { + fun setFilterSettings(item: ItemStack, filter: ItemFilterSet) { item.set(MDataComponentTypes.ITEM_FILTER, filter) } @Suppress("unused") companion object { const val MAX_FILTERS = 4 * 3 - private val EMPTY_FILTER = ItemFilter(MAX_FILTERS) + private val EMPTY_FILTER = ItemFilterSet.EMPTY internal fun onPickupEvent(event: ItemEntityPickupEvent.Pre) { if (event.itemEntity.owner != null && event.itemEntity.owner != event.player && event.itemEntity.age < 200 || event.itemEntity.item.isEmpty) { @@ -83,9 +83,9 @@ class PortableCondensationDriveItem(capacity: Int) : Item(Properties().stacksTo( var doBreak = false stack.getCapability(MatteryCapability.CONDENSATION_DRIVE)?.let { - val filter = stack[MDataComponentTypes.ITEM_FILTER] ?: ItemFilter.EMPTY + val filter = stack[MDataComponentTypes.ITEM_FILTER] ?: ItemFilterSet.EMPTY - if (filter.match(event.itemEntity.item)) { + if (filter.test(event.itemEntity.item)) { val copy = event.itemEntity.item.copy() val remaining = (it as ItemMatteryDrive).insertStack(event.itemEntity.item, false) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/QuickMoveInput.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/QuickMoveInput.kt index 7deac409c..3d49f5cd8 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/QuickMoveInput.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/QuickMoveInput.kt @@ -11,9 +11,9 @@ import ru.dbotthepony.mc.otm.container.IFilteredContainerSlot import ru.dbotthepony.mc.otm.container.util.containerSlotOrNull import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.core.isNotEmpty -import ru.dbotthepony.mc.otm.core.util.ItemStackKey -import ru.dbotthepony.mc.otm.core.util.asKey -import ru.dbotthepony.mc.otm.core.util.asKeyOrNull +import ru.dbotthepony.mc.otm.container.ItemStackKey +import ru.dbotthepony.mc.otm.container.asKey +import ru.dbotthepony.mc.otm.container.asKeyOrNull class QuickMoveInput(private val menu: MatteryMenu, val from: Collection, val to: Collection, val mode: Mode, val dontTouchFilteredSlots: Boolean = true) { /** @@ -36,8 +36,8 @@ class QuickMoveInput(private val menu: MatteryMenu, val from: Collection, val slotA = a.containerSlotOrNull() val slotB = b.containerSlotOrNull() - val hasFilterA = slotA is IFilteredContainerSlot && slotA.hasFilter - val hasFilterB = slotB is IFilteredContainerSlot && slotB.hasFilter + val hasFilterA = slotA is IFilteredContainerSlot && slotA.filter.hasRules + val hasFilterB = slotB is IFilteredContainerSlot && slotB.filter.hasRules return hasFilterB.compareTo(hasFilterA) } @@ -51,19 +51,22 @@ class QuickMoveInput(private val menu: MatteryMenu, val from: Collection, override fun move(from: Collection, to: Collection, player: Player, dontTouchFilteredSlots: Boolean) { if (from.isEmpty() || to.isEmpty()) return val (_, itemsFrom) = computeSlotLists(from, dontTouchFilteredSlots) - val (_, itemsTo) = computeSlotLists(to, false) + val (_, itemsTo, filteredTo) = computeSlotLists(to, false) - val intersect = if (itemsFrom.size < itemsTo.size) itemsFrom.keys.filter { it in itemsTo.keys } else itemsTo.keys.filter { it in itemsFrom.keys } + val intersect: Collection + + if (filteredTo.isNotEmpty()) + intersect = itemsFrom.keys + else if (itemsFrom.size < itemsTo.size) + intersect = itemsFrom.keys.filter { it in itemsTo.keys } + else + intersect = itemsTo.keys.filter { it in itemsFrom.keys } for (key in intersect) { - val slotsTo = itemsTo[key]!! + val slotsTo = ArrayList(itemsTo[key] ?: listOf()) + slotsTo.addAll(0, filteredTo) val slotsFrom = itemsFrom[key]!! - if (!dontTouchFilteredSlots) { - // touch filtered slots last - slotsFrom.sortWith(HasFilterComparator.reversed()) - } - slotsFrom.forEach { moveItemStackTo(player, it, slotsTo) } } } @@ -76,18 +79,23 @@ class QuickMoveInput(private val menu: MatteryMenu, val from: Collection, override fun move(from: Collection, to: Collection, player: Player, dontTouchFilteredSlots: Boolean) { if (from.isEmpty() || to.isEmpty()) return val (_, itemsFrom) = computeSlotLists(from, dontTouchFilteredSlots) - val (emptyTo, itemsTo) = computeSlotLists(to, false) + val (emptyTo, itemsTo, filteredTo) = computeSlotLists(to, false) - val intersect = if (itemsFrom.size < itemsTo.size) itemsFrom.keys.filter { it in itemsTo.keys } else itemsTo.keys.filter { it in itemsFrom.keys } + val intersect: Collection + + if (filteredTo.isNotEmpty()) + intersect = itemsFrom.keys + else if (itemsFrom.size < itemsTo.size) + intersect = itemsFrom.keys.filter { it in itemsTo.keys } + else + intersect = itemsTo.keys.filter { it in itemsFrom.keys } for (key in intersect) { - val slotsTo = prioritySortSlots(itemsTo[key]!!, key.asItemStack()) - val slotsFrom = itemsFrom[key]!! + val slotsTo = ArrayList(itemsTo[key] ?: listOf()).also { it.addAll(0, filteredTo) } + if (slotsTo.isEmpty()) continue + prioritySortSlotsInPlace(slotsTo, key.asItemStack()) - if (!dontTouchFilteredSlots) { - // touch filtered slots last - slotsFrom.sortWith(HasFilterComparator.reversed()) - } + val slotsFrom = itemsFrom[key]!! slotsFrom.removeIf { moveItemStackTo(player, it, slotsTo, sort = false); it.item.isEmpty } var moveAny = false @@ -108,7 +116,7 @@ class QuickMoveInput(private val menu: MatteryMenu, val from: Collection, from.forEach { val slot = it.containerSlotOrNull() - if (!dontTouchFilteredSlots || slot !is IFilteredContainerSlot || !slot.hasFilter) + if (!dontTouchFilteredSlots || slot !is IFilteredContainerSlot || !slot.filter.hasRules) moveItemStackTo(player, it, toSorted, sort = false) } } @@ -135,31 +143,42 @@ class QuickMoveInput(private val menu: MatteryMenu, val from: Collection, mode.move(from, to, menu.player, dontTouchFilteredSlots) } + private data class SlotLists( + val empty: MutableList, + val withItems: MutableMap>, + val withFilters: MutableList, + ) + companion object { fun create(menu: MatteryMenu, from: Collection, to: Collection, dontTouchFilteredSlots: Boolean = true): Map { return Mode.entries.associateWith { QuickMoveInput(menu, from, to, it, dontTouchFilteredSlots) } } - private fun computeSlotLists(slots: Collection, skipFilteredSlots: Boolean): Pair, MutableMap>> { + private fun computeSlotLists(slots: Collection, skipFilteredSlots: Boolean): SlotLists { val emptySlots = ArrayList() + val filteredSlots = ArrayList() val filledSlots = HashMap>() for (slot in slots) { val underlyingSlot = slot.containerSlotOrNull() - if (underlyingSlot is IFilteredContainerSlot && (underlyingSlot.filter == Items.AIR || underlyingSlot.filter != null && skipFilteredSlots)) + if (underlyingSlot is IFilteredContainerSlot && (underlyingSlot.filter.denyAll || !underlyingSlot.filter.allowAll && skipFilteredSlots)) continue - val key = slot.item.asKeyOrNull() ?: (underlyingSlot as? IFilteredContainerSlot)?.filter?.asKey() + val key = slot.item.asKeyOrNull() if (key == null) { - emptySlots.add(slot) + if (underlyingSlot is IFilteredContainerSlot && underlyingSlot.filter.hasRules) { + filteredSlots.add(slot) + } else { + emptySlots.add(slot) + } } else { filledSlots.computeIfAbsent(key) { ArrayList() }.add(slot) } } - return emptySlots to filledSlots + return SlotLists(emptySlots, filledSlots, filteredSlots) } fun moveItemStackTo( @@ -195,7 +214,10 @@ class QuickMoveInput(private val menu: MatteryMenu, val from: Collection, fun > prioritySortSlotsInPlace(slots: T, filterItem: ItemStack? = null): T { slots.removeIf { val slot = it.containerSlotOrNull() - it.isOverCapacity || filterItem != null && !it.mayPlace(filterItem) || slot is IFilteredContainerSlot && slot.isForbiddenForAutomation + + it.isOverCapacity || + filterItem != null && !it.mayPlace(filterItem) || + slot is IFilteredContainerSlot && (slot.filter.denyAll || filterItem != null && !slot.filter.test(filterItem)) } slots.sortWith(itemFilterSlotComparator) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/Slots.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/Slots.kt index 2ad6091ee..64f4bc51e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/Slots.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/Slots.kt @@ -7,7 +7,6 @@ import net.minecraft.world.entity.player.Player import net.minecraft.world.inventory.Slot import net.minecraft.world.item.Item import net.minecraft.world.item.ItemStack -import net.neoforged.neoforge.capabilities.Capabilities import ru.dbotthepony.kommons.util.Delegate import ru.dbotthepony.kommons.util.getValue import ru.dbotthepony.kommons.util.value @@ -17,11 +16,11 @@ import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.capability.UpgradeType import ru.dbotthepony.mc.otm.capability.energy import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage -import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.container.EnhancedContainer import ru.dbotthepony.mc.otm.container.IEnhancedContainer import ru.dbotthepony.mc.otm.container.IFilteredContainerSlot import ru.dbotthepony.mc.otm.container.ItemFilter +import ru.dbotthepony.mc.otm.container.ItemFilterSet import ru.dbotthepony.mc.otm.container.UpgradeContainer import ru.dbotthepony.mc.otm.container.util.containerSlotOrNull import ru.dbotthepony.mc.otm.core.collect.ConditionalEnumSet @@ -30,9 +29,9 @@ import ru.dbotthepony.mc.otm.core.isNotEmpty import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback import ru.dbotthepony.mc.otm.menu.input.InstantBooleanInput +import ru.dbotthepony.mc.otm.menu.input.ItemFilterInput import ru.dbotthepony.mc.otm.network.StreamCodecs import ru.dbotthepony.mc.otm.player.IPlayerInventorySlot -import ru.dbotthepony.mc.otm.runOnClient import java.util.* import java.util.function.BooleanSupplier import java.util.function.DoubleSupplier @@ -61,7 +60,7 @@ open class MatteryMenuSlot(container: Container, index: Int, x: Int = 0, y: Int val slot = containerSlotOrNull() if (slot is IFilteredContainerSlot && slot !is IPlayerInventorySlot) { - menu.mSynchronizer.add(Delegate.Of(slot::filter), StreamCodecs.ITEM_TYPE_NULLABLE) + menu.mSynchronizer.add(Delegate.Of(slot::filter), StreamCodecs.ITEM_FILTER) } } @@ -113,7 +112,7 @@ open class MatteryMenuSlot(container: Container, index: Int, x: Int = 0, y: Int } open class UserFilteredMenuSlot(container: Container, index: Int, x: Int = 0, y: Int = 0) : MatteryMenuSlot(container, index, x, y) { - var filterInput: MatteryMenu.PlayerInput? = null + var filterInput: MatteryMenu.PlayerInput? = null private set override fun setupNetworkControls(menu: MatteryMenu) { @@ -121,7 +120,7 @@ open class UserFilteredMenuSlot(container: Container, index: Int, x: Int = 0, y: val slot = containerSlotOrNull() if (slot is IFilteredContainerSlot) { - filterInput = menu.PlayerInput(StreamCodecs.ITEM_TYPE_NULLABLE, handler = { slot.filter = it }) + filterInput = menu.PlayerInput(StreamCodecs.ITEM_FILTER, handler = { slot.filter = it }) } } } @@ -184,63 +183,16 @@ open class DriveMenuSlot(container: Container, index: Int, x: Int = 0, y: Int = } } -fun MatteryMenu.addFilterSlots(slots: Delegate): List> { - val result = ArrayList>(slots.value.size) - - for (i in 0 until slots.value.size) { - result.add(Delegate.Of( - mSynchronizer.computedItem { slots.value[i] }, - itemStackInput { slots.value = slots.value.set(i, it) } - )) - } - - return result +fun MatteryMenu.addFilterSlots(amount: Int, slots: Delegate?): ItemFilterInput { + return ItemFilterInput(this, amount, slots) } -fun MatteryMenu.addFilterSlots(amount: Int): List> { - val result = ArrayList>(amount) - - for (i in 0 until amount) { - result.add(Delegate.Of( - mSynchronizer.computedItem { ItemStack.EMPTY }, - itemStackInput { throw UnsupportedOperationException() } - )) - } - - return result +fun MatteryMenu.addFilterSlots(amount: Int): ItemFilterInput { + return addFilterSlots(amount, Delegate.Box(ItemFilterSet.EMPTY)) } -fun MatteryMenu.addFilterSlots(slots: Delegate?, amount: Int): List> { - if (slots != null && amount != slots.value.size) - throw IllegalStateException("Provided ItemFiler has different amount of slots than expected: ${slots.value.size} != $amount") - - if (slots == null) - return addFilterSlots(amount) - else - return addFilterSlots(slots) -} - -fun MatteryMenu.addFilterSlots(slots: KMutableProperty0?, amount: Int): List> { - return addFilterSlots(if (slots == null) null else Delegate.Of(slots), amount) -} - -data class FilterControls(val slots: List>, val isWhitelist: BooleanInputWithFeedback, val matchComponents: BooleanInputWithFeedback, val matchTag: BooleanInputWithFeedback) - -fun MatteryMenu.addFilterControls(slots: Delegate?, amount: Int): FilterControls { - if (slots == null) { - return FilterControls(addFilterSlots(amount), BooleanInputWithFeedback(this), BooleanInputWithFeedback(this), BooleanInputWithFeedback(this)) - } else { - return FilterControls( - addFilterSlots(slots, amount), - BooleanInputWithFeedback.dispatch(this, slots, ItemFilter::isWhitelist, ItemFilter::isWhitelist), - BooleanInputWithFeedback.dispatch(this, slots, ItemFilter::matchComponents, ItemFilter::matchComponents), - BooleanInputWithFeedback.dispatch(this, slots, ItemFilter::matchTag, ItemFilter::matchTag), - ) - } -} - -fun MatteryMenu.addFilterControls(slots: KMutableProperty0?, amount: Int): FilterControls { - return addFilterControls(slots?.let { Delegate.Of(it) }, amount) +fun MatteryMenu.addFilterSlots(amount: Int, slots: KMutableProperty0?): ItemFilterInput { + return addFilterSlots(amount, if (slots == null) null else Delegate.Of(slots)) } val Slot.isOverCapacity: Boolean get() { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/ItemFilterInput.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/ItemFilterInput.kt new file mode 100644 index 000000000..43c96518c --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/ItemFilterInput.kt @@ -0,0 +1,39 @@ +package ru.dbotthepony.mc.otm.menu.input + +import net.minecraft.world.entity.player.Player +import ru.dbotthepony.kommons.util.Delegate +import ru.dbotthepony.mc.otm.container.ItemFilter +import ru.dbotthepony.mc.otm.container.ItemFilterSet +import ru.dbotthepony.mc.otm.core.immutableList +import ru.dbotthepony.mc.otm.menu.MatteryMenu +import ru.dbotthepony.mc.otm.network.StreamCodecs +import java.util.function.Predicate + +class ItemFilterInput(menu: MatteryMenu, maxSlots: Int, var filter: Delegate?, var allowRecursive: Boolean = false) { + val synchers = immutableList(maxSlots) { i -> + menu.mSynchronizer.computed({ filter?.get()?.get(i) ?: ItemFilter.EMPTY }, StreamCodecs.ITEM_FILTER) + } + + val inputs = immutableList(maxSlots) { i -> + menu.PlayerInput(StreamCodecs.ITEM_FILTER, handler = { + if (allowRecursive || it.depth <= 1) + filter?.get()?.addOrReplace(i, it) + }) + } + + val slots = immutableList(maxSlots) { i -> + Delegate.Of(synchers[i], inputs[i]) + } + + val isWhitelist = BooleanInputWithFeedback.dispatch( + menu, + Delegate.Of({ filter?.get() ?: ItemFilterSet.EMPTY }, { filter?.accept(it) }), + { it.isWhitelist }, + { it, v -> it.isWhitelist(v) } + ) + + fun filter(predicate: Predicate) { + inputs.forEach { it.filter(predicate) } + isWhitelist.input.filter(predicate) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/DriveViewerMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/DriveViewerMenu.kt index 1f0a190c0..1e10b89dc 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/DriveViewerMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/DriveViewerMenu.kt @@ -1,6 +1,5 @@ package ru.dbotthepony.mc.otm.menu.storage -import net.minecraft.world.SimpleContainer import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Player import net.minecraft.world.item.ItemStack @@ -14,6 +13,7 @@ import ru.dbotthepony.mc.otm.capability.drive.IMatteryDrive import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage import ru.dbotthepony.mc.otm.container.EnhancedContainer import ru.dbotthepony.mc.otm.container.ItemFilter +import ru.dbotthepony.mc.otm.container.ItemFilterSet import ru.dbotthepony.mc.otm.core.immutableList import ru.dbotthepony.mc.otm.core.util.ItemStorageStackSorter import ru.dbotthepony.mc.otm.item.PortableCondensationDriveItem @@ -24,7 +24,9 @@ import ru.dbotthepony.mc.otm.menu.data.NetworkedItemView import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback +import ru.dbotthepony.mc.otm.menu.input.ItemFilterInput import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget +import ru.dbotthepony.mc.otm.network.StreamCodecs import ru.dbotthepony.mc.otm.registry.game.MMenus import ru.dbotthepony.mc.otm.storage.ItemStorageStack import ru.dbotthepony.mc.otm.storage.StorageStack @@ -75,26 +77,21 @@ class DriveViewerMenu( var drivePresent by mSynchronizer.boolean() - private fun getFilter(): ItemFilter? { + private fun getFilter(): ItemFilterSet { val stack = (tile as? DriveViewerBlockEntity)?.container?.getItem(0) - return (stack?.item as? PortableCondensationDriveItem)?.getFilterSettings(stack) + return (stack?.item as? PortableCondensationDriveItem)?.getFilterSettings(stack) ?: ItemFilterSet.EMPTY } - private fun setFilter(value: ItemFilter) { + private fun setFilter(value: ItemFilterSet) { val stack = (tile as? DriveViewerBlockEntity)?.container?.getItem(0) (stack?.item as? PortableCondensationDriveItem)?.setFilterSettings(stack, value) } - val driveFilterSlots = immutableList(PortableCondensationDriveItem.MAX_FILTERS) { i -> - Delegate.Of( - mSynchronizer.computedItem { getFilter()?.get(i) ?: ItemStack.EMPTY }, - itemStackInput { getFilter()?.set(i, it) }.filter { drivePresent } - ) - } + val driveFilter = ItemFilterInput(this, PortableCondensationDriveItem.MAX_FILTERS, Delegate.Of(::getFilter, ::setFilter)) - val isWhitelist = BooleanInputWithFeedback.dispatch(this, Delegate.Of({ getFilter() }, { setFilter(it!!) }), { it?.isWhitelist ?: false }, { it, v -> it?.isWhitelist(v) }).also { it.filter { drivePresent } } - val matchTag = BooleanInputWithFeedback.dispatch(this, Delegate.Of({ getFilter() }, { setFilter(it!!) }), { it?.matchTag ?: false }, { it, v -> it?.matchTag(v) }).also { it.filter { drivePresent } } - val matchComponents = BooleanInputWithFeedback.dispatch(this, Delegate.Of({ getFilter() }, { setFilter(it!!) }), { it?.matchComponents ?: false }, { it, v -> it?.matchComponents(v) }).also { it.filter { drivePresent } } + init { + driveFilter.filter { drivePresent } + } override fun broadcastChanges() { super.broadcastChanges() diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/StorageBusMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/StorageBusMenu.kt index 27f750294..b091300c7 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/StorageBusMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/StorageBusMenu.kt @@ -4,7 +4,7 @@ import net.minecraft.world.entity.player.Inventory import ru.dbotthepony.mc.otm.block.entity.storage.StorageBusBlockEntity import ru.dbotthepony.mc.otm.capability.FlowDirection import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu -import ru.dbotthepony.mc.otm.menu.addFilterControls +import ru.dbotthepony.mc.otm.menu.addFilterSlots import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback import ru.dbotthepony.mc.otm.menu.input.IntInputWithFeedback @@ -16,7 +16,7 @@ class StorageBusMenu( inventory: Inventory, tile: StorageBusBlockEntity? = null ) : MatteryPoweredMenu(MMenus.STORAGE_BUS, containerId, inventory, tile) { - val filter = addFilterControls(tile?.let { it::filter }, StorageBusBlockEntity.MAX_FILTERS) + val filter = addFilterSlots(StorageBusBlockEntity.MAX_FILTERS, tile?.let { it::filter }) val insertPriority = IntInputWithFeedback(this, tile?.let { it::insertPriority }) val extractPriority = IntInputWithFeedback(this, tile?.let { it::extractPriority }) val profiledEnergy = ProfiledLevelGaugeWidget(this, tile?.energy, energyWidget) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/StorageImporterExporterMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/StorageImporterExporterMenu.kt index 3f5d89d62..1337d6d83 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/StorageImporterExporterMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/StorageImporterExporterMenu.kt @@ -3,7 +3,7 @@ package ru.dbotthepony.mc.otm.menu.storage import net.minecraft.world.entity.player.Inventory import ru.dbotthepony.mc.otm.block.entity.storage.AbstractStorageImportExport import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu -import ru.dbotthepony.mc.otm.menu.addFilterControls +import ru.dbotthepony.mc.otm.menu.addFilterSlots import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget import ru.dbotthepony.mc.otm.registry.game.MMenus @@ -11,7 +11,7 @@ import ru.dbotthepony.mc.otm.registry.game.MMenus class StorageImporterExporterMenu( containerId: Int, inventory: Inventory, tile: AbstractStorageImportExport? = null ) : MatteryPoweredMenu(MMenus.STORAGE_IMPORTER_EXPORTER, containerId, inventory, tile) { - val filter = addFilterControls(tile?.let { it::filter }, AbstractStorageImportExport.MAX_FILTERS) + val filter = addFilterSlots(AbstractStorageImportExport.MAX_FILTERS, tile?.let { it::filter }) val profiledEnergy = ProfiledLevelGaugeWidget(this, tile?.energy, energyWidget) val energyConfig = EnergyConfigPlayerInput(this, tile?.energyConfig) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryPlayerPackets.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryPlayerPackets.kt index bf17ce3bb..2c796bf21 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryPlayerPackets.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryPlayerPackets.kt @@ -484,7 +484,7 @@ class QuickStackPacket( slots.forEach { val slot = it.containerSlotOrNull() - if (it.hasItem() || slot is IFilteredContainerSlot && slot.hasFilter) + if (it.hasItem() || slot is IFilteredContainerSlot && !slot.filter.allowAll) prioritySlots.add(it) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/StreamCodecs.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/StreamCodecs.kt index 8fffdd713..7479ced2e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/network/StreamCodecs.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/StreamCodecs.kt @@ -9,6 +9,7 @@ import net.minecraft.resources.ResourceLocation import net.minecraft.world.level.block.Block import net.minecraft.world.level.block.state.BlockState import ru.dbotthepony.kommons.math.RGBAColor +import ru.dbotthepony.mc.otm.container.ItemFilter import ru.dbotthepony.mc.otm.core.util.readDecimal import ru.dbotthepony.mc.otm.core.util.writeDecimal import ru.dbotthepony.mc.otm.core.readBlockType @@ -43,6 +44,8 @@ object StreamCodecs { val ITEM_TYPE_NULLABLE = ITEM_TYPE.nullable() val DECIMAL = StreamCodec.of(FriendlyByteBuf::writeDecimal, FriendlyByteBuf::readDecimal).wrap() + val ITEM_FILTER = ByteBufCodecs.fromCodecWithRegistries(ItemFilter.CODEC).wrap() + fun composite( c0: StreamCodec, g0: (T) -> T0, c1: StreamCodec, g1: (T) -> T1, diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/player/ExopackContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/player/ExopackContainer.kt index f7c86e5b2..f2477935a 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/player/ExopackContainer.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/player/ExopackContainer.kt @@ -4,6 +4,7 @@ import net.minecraft.world.item.Item import ru.dbotthepony.mc.otm.container.EnhancedContainer import ru.dbotthepony.mc.otm.container.IContainerSlot import ru.dbotthepony.mc.otm.container.IEnhancedContainer +import ru.dbotthepony.mc.otm.container.ItemFilter class ExopackContainer(size: Int, val player: MatteryPlayer) : EnhancedContainer(size) { private inner class Slot(slot: Int) : IContainerSlot.Simple(slot, this@ExopackContainer), IPlayerInventorySlot { @@ -11,9 +12,9 @@ class ExopackContainer(size: Int, val player: MatteryPlayer) : EnhancedContainer get() = (PlayerInventoryWrapper.SLOTS + slot) in player.slotsChargeFlag set(value) { if (value) player.slotsChargeFlag.add(PlayerInventoryWrapper.SLOTS + slot) else player.slotsChargeFlag.remove(PlayerInventoryWrapper.SLOTS + slot) } - override var filter: Item? - get() = player.slotFilters[PlayerInventoryWrapper.SLOTS + slot] - set(value) { if (value == null) player.slotFilters.remove(PlayerInventoryWrapper.SLOTS + slot) else player.slotFilters[PlayerInventoryWrapper.SLOTS + slot] = value } + override var filter: ItemFilter + get() = player.slotFilters[PlayerInventoryWrapper.SLOTS + slot] ?: ItemFilter.EMPTY + set(value) { if (value.allowAll) player.slotFilters.remove(PlayerInventoryWrapper.SLOTS + slot) else player.slotFilters[PlayerInventoryWrapper.SLOTS + slot] = value } } override fun containerSlot(slot: Int): IPlayerInventorySlot { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/player/MatteryPlayer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/player/MatteryPlayer.kt index 9e2868e7f..7f488e078 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/player/MatteryPlayer.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/player/MatteryPlayer.kt @@ -83,6 +83,7 @@ import ru.dbotthepony.mc.otm.container.EnhancedContainer import ru.dbotthepony.mc.otm.container.IContainerSlot import ru.dbotthepony.mc.otm.container.IEnhancedContainer import ru.dbotthepony.mc.otm.container.IFilteredContainerSlot +import ru.dbotthepony.mc.otm.container.ItemFilter import ru.dbotthepony.mc.otm.container.get import ru.dbotthepony.mc.otm.container.slotted.ContainerSlot import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer @@ -254,7 +255,7 @@ class MatteryPlayer(val ply: Player) { val slotFilters = syncher.map( backing = ListenableMap(Int2ObjectOpenHashMap()), keyCodec = StreamCodecs.VAR_INT, - valueCodec = StreamCodecs.ITEM_TYPE + valueCodec = StreamCodecs.ITEM_FILTER ).delegate private fun slotChargeToDefault() { @@ -1299,11 +1300,11 @@ class MatteryPlayer(val ply: Player) { @Suppress("unused") companion object { - private val filtersCodec: Codec>> = Codec.list( + private val filtersCodec: Codec>> = Codec.list( RecordCodecBuilder.create { it.group( Codec.INT.minRange(0).fieldOf("slot").forGetter { it.first }, - BuiltInRegistries.ITEM.byNameCodec().fieldOf("filter").forGetter { it.second } + ItemFilter.CODEC.fieldOf("filter").forGetter { it.second } ).apply(it, ::Pair) } ) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/player/PlayerInventoryWrapper.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/player/PlayerInventoryWrapper.kt index 35b15d786..4371b50e5 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/player/PlayerInventoryWrapper.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/player/PlayerInventoryWrapper.kt @@ -5,6 +5,7 @@ import net.minecraft.world.entity.player.Player import net.minecraft.world.item.Item import net.minecraft.world.item.ItemStack import ru.dbotthepony.mc.otm.container.ISlottedContainer +import ru.dbotthepony.mc.otm.container.ItemFilter import ru.dbotthepony.mc.otm.container.get import ru.dbotthepony.mc.otm.container.set @@ -17,9 +18,9 @@ class PlayerInventoryWrapper(val player: MatteryPlayer) : ISlottedContainer>("android_research_result") val ANDROID_FEATURE = k>("android_feature") val STACK_TYPE = k>("stack_type") + val ITEM_FILTER = k>("item_filter") } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/game/MDataComponentTypes.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/game/MDataComponentTypes.kt index fbf77afc9..92fbcbfcc 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/game/MDataComponentTypes.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/game/MDataComponentTypes.kt @@ -4,7 +4,6 @@ import com.google.common.collect.ImmutableList import com.mojang.serialization.Codec import net.minecraft.core.UUIDUtil import net.minecraft.core.component.DataComponentType -import net.minecraft.core.component.DataComponents import net.minecraft.core.registries.BuiltInRegistries import net.minecraft.nbt.CompoundTag import net.minecraft.network.RegistryFriendlyByteBuf @@ -15,7 +14,7 @@ import net.neoforged.bus.api.IEventBus import net.neoforged.neoforge.fluids.SimpleFluidContent import ru.dbotthepony.mc.otm.capability.FlowDirection import ru.dbotthepony.mc.otm.capability.matter.PatternState -import ru.dbotthepony.mc.otm.container.ItemFilter +import ru.dbotthepony.mc.otm.container.ItemFilterSet import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.data.codec.DecimalCodec import ru.dbotthepony.mc.otm.item.tool.RedstoneInteractorItem @@ -80,7 +79,7 @@ object MDataComponentTypes { DataComponentType.builder>().persistent(Codec.list(PatternState.CODEC).xmap({ ImmutableList.copyOf(it) }, { it })).build() } - val ITEM_FILTER: DataComponentType by registry.register("item_filter") { DataComponentType.builder().persistent(ItemFilter.CODEC).build() } + val ITEM_FILTER: DataComponentType by registry.register("item_filter") { DataComponentType.builder().persistent(ItemFilterSet.CODEC).build() } val TICK_TIMER: DataComponentType by registry.register("tick_timer") { DataComponentType.builder().persistent(RedstoneInteractorItem.TickTimer.CODEC).build() } val EXPERIENCE: DataComponentType by registry.register("experience") { DataComponentType.builder().persistent(Codec.LONG).networkSynchronized(StreamCodecs.LONG).build() }