Initial implementation for improved item filters

This commit is contained in:
DBotThePony 2025-03-29 10:55:32 +07:00
parent 3e593748f7
commit 78fad5d3cc
Signed by: DBot
GPG Key ID: DCC23B5715498507
36 changed files with 610 additions and 335 deletions

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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<ItemStorageStack>) {
if (filter.match(stack.toItemStack())) {
if (filter.test(stack.toItemStack())) {
relevantTuples.add(id)
}
}

View File

@ -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

View File

@ -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

View File

@ -149,10 +149,10 @@ fun Player.items(includeCosmetics: Boolean = true): Iterator<ItemStack> {
val matteryPlayer = matteryPlayer
val iterators = ArrayList<Iterator<ItemStack>>()
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<out Aware
val streams = ArrayList<Stream<out AwareItemStack>>()
streams.add(inventory.awareStream())
matteryPlayer?.let {
if (it.hasExopack) {
streams.add(it.exopackContainer.awareStream())
}
if (matteryPlayer.hasExopack) {
streams.add(matteryPlayer.exopackContainer.awareStream())
}
if (isCuriosLoaded) {

View File

@ -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<out S : MatteryScreen<*>> @JvmOverloads constructor(
open class FilterSlotPanel<out S : MatteryScreen<*>>(
screen: S,
parent: EditablePanel<*>?,
val slot: Delegate<ItemStack>,
val slot: Delegate<ItemFilter>,
x: Float = 0f,
y: Float = 0f,
width: Float = SIZE,
height: Float = SIZE
) : AbstractSlotPanel<S>(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
}
}

View File

@ -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 S : MatteryScreen<*>, 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 S : MatteryScreen<*>, 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 S : MatteryScreen<*>, 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<Component>
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<Component>().also {

View File

@ -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 S : MatteryScreen<*>, out T : UserFilteredM
height: Float = SIZE,
) : SlotPanel<S, T>(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()

View File

@ -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)))

View File

@ -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

View File

@ -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> = 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())

View File

@ -282,9 +282,9 @@ interface IEnhancedContainer<out S : IContainerSlot> : 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<out S : IContainerSlot> : 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
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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<ItemStack>, 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<ItemStack>, 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<ItemStack> {
interface Type<T : ItemFilter> {
val codec: MapCodec<T>
}
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<ItemStackKey>
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<ItemStackKey>
get() = ImmutableSet.of(ItemStackKey(item))
companion object : Type<Item> {
override val codec: MapCodec<Item> by lazy {
BuiltInRegistries.ITEM.byNameCodec().xmap(::Item, Item::item).fieldOf("item")
}
}
}
val size: Int
get() = filter.size
private data class Tag(val tag: TagKey<net.minecraft.world.item.Item>) : 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<ItemStackKey> get() {
return BuiltInRegistries.ITEM
.getTag(tag)
.map { it.stream().map { ItemStackKey(it.value()) }.collect(ImmutableSet.toImmutableSet()) }
.orElseGet { ImmutableSet.of() }
}
companion object : Type<Tag> {
override val codec: MapCodec<Tag> 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<DenyAll> {
override val type: Type<*>
get() = this
private fun copy(
filter: Array<ItemStack> = this.filter,
isWhitelist: Boolean = this.isWhitelist,
matchTag: Boolean = this.matchTag,
matchComponents: Boolean = this.matchComponents,
) = ItemFilter(filter, isWhitelist, matchTag, matchComponents)
override val codec: MapCodec<DenyAll> = 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<TagKey<Item>>()
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<Companion> {
const val MAX_DEPTH = 16
private fun roll(input: ItemFilter): Either<ItemFilter.Item, ItemFilter> {
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<ItemFilter> by lazy {
RecordCodecBuilder.create<ItemFilter> {
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<Companion> = 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<net.minecraft.world.item.Item>): ItemFilter {
return Tag(tag)
}
}
}

View File

@ -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<ItemFilter>, val isWhitelist: Boolean = false) : ItemFilter {
constructor(list: Collection<ItemFilter>, 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<ItemStackKey> get() {
val sub = filter.map { it.displayItems }
val results = ArrayList<ItemStackKey>(sub.sumOf { it.size })
sub.forEach { results.addAll(it) }
return ImmutableSet.copyOf(results)
}
companion object : ItemFilter.Type<ItemFilterSet> {
override val codec: MapCodec<ItemFilterSet> 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<ItemFilterSet> by lazy {
codec.codec()
}
}
}

View File

@ -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<ItemStackKey> = ImmutableSet.of(this)
companion object : ItemFilter.Type<ItemStackKey> {
override val codec: MapCodec<ItemStackKey>
get() = MAP_CODEC
val MAP_CODEC: MapCodec<ItemStackKey> 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<ItemStackKey> by lazy {
MAP_CODEC.codec()
}
}
}
fun ItemStack.asKey(): ItemStackKey {

View File

@ -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()
}
}

View File

@ -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)
}
}
}

View File

@ -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)

View File

@ -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<Slot>, val to: Collection<Slot>, val mode: Mode, val dontTouchFilteredSlots: Boolean = true) {
/**
@ -36,8 +36,8 @@ class QuickMoveInput(private val menu: MatteryMenu, val from: Collection<Slot>,
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<Slot>,
override fun move(from: Collection<Slot>, to: Collection<Slot>, 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<ItemStackKey>
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<Slot>,
override fun move(from: Collection<Slot>, to: Collection<Slot>, 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<ItemStackKey>
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<Slot>,
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<Slot>,
mode.move(from, to, menu.player, dontTouchFilteredSlots)
}
private data class SlotLists(
val empty: MutableList<Slot>,
val withItems: MutableMap<ItemStackKey, MutableList<Slot>>,
val withFilters: MutableList<Slot>,
)
companion object {
fun create(menu: MatteryMenu, from: Collection<Slot>, to: Collection<Slot>, dontTouchFilteredSlots: Boolean = true): Map<Mode, QuickMoveInput> {
return Mode.entries.associateWith { QuickMoveInput(menu, from, to, it, dontTouchFilteredSlots) }
}
private fun computeSlotLists(slots: Collection<Slot>, skipFilteredSlots: Boolean): Pair<MutableList<Slot>, MutableMap<ItemStackKey, MutableList<Slot>>> {
private fun computeSlotLists(slots: Collection<Slot>, skipFilteredSlots: Boolean): SlotLists {
val emptySlots = ArrayList<Slot>()
val filteredSlots = ArrayList<Slot>()
val filledSlots = HashMap<ItemStackKey, MutableList<Slot>>()
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<Slot>,
fun <T : MutableList<Slot>> 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)

View File

@ -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<Item?>? = null
var filterInput: MatteryMenu.PlayerInput<ItemFilter>? = 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<ItemFilter>): List<Delegate<ItemStack>> {
val result = ArrayList<Delegate<ItemStack>>(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<ItemFilterSet>?): ItemFilterInput {
return ItemFilterInput(this, amount, slots)
}
fun MatteryMenu.addFilterSlots(amount: Int): List<Delegate<ItemStack>> {
val result = ArrayList<Delegate<ItemStack>>(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<ItemFilter>?, amount: Int): List<Delegate<ItemStack>> {
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<ItemFilter>?, amount: Int): List<Delegate<ItemStack>> {
return addFilterSlots(if (slots == null) null else Delegate.Of(slots), amount)
}
data class FilterControls(val slots: List<Delegate<ItemStack>>, val isWhitelist: BooleanInputWithFeedback, val matchComponents: BooleanInputWithFeedback, val matchTag: BooleanInputWithFeedback)
fun MatteryMenu.addFilterControls(slots: Delegate<ItemFilter>?, 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<ItemFilter>?, amount: Int): FilterControls {
return addFilterControls(slots?.let { Delegate.Of(it) }, amount)
fun MatteryMenu.addFilterSlots(amount: Int, slots: KMutableProperty0<ItemFilterSet>?): ItemFilterInput {
return addFilterSlots(amount, if (slots == null) null else Delegate.Of(slots))
}
val Slot.isOverCapacity: Boolean get() {

View File

@ -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<ItemFilterSet>?, 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<Player>) {
inputs.forEach { it.filter(predicate) }
isWhitelist.input.filter(predicate)
}
}

View File

@ -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()

View File

@ -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)

View File

@ -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)

View File

@ -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)
}

View File

@ -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 <S : ByteBuf, T, T0, T1, T2, T3, T4, T5, T6> composite(
c0: StreamCodec<in S, T0>, g0: (T) -> T0,
c1: StreamCodec<in S, T1>, g1: (T) -> T1,

View File

@ -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<IPlayerInventorySlot>(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 {

View File

@ -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<List<Pair<Int, Item>>> = Codec.list(
private val filtersCodec: Codec<List<Pair<Int, ItemFilter>>> = 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)
}
)

View File

@ -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<IPla
inventory.setChanged()
}
override var filter: Item?
get() = player.slotFilters[slot]
set(value) { if (value == null) player.slotFilters.remove(slot) else player.slotFilters[slot] = value }
override var filter: ItemFilter
get() = player.slotFilters[slot] ?: ItemFilter.EMPTY
set(value) { if (value.allowAll) player.slotFilters.remove(slot) else player.slotFilters[slot] = value }
override var shouldCharge: Boolean
get() = slot in player.slotsChargeFlag
set(value) { if (value) player.slotsChargeFlag.add(slot) else player.slotsChargeFlag.remove(slot) }

View File

@ -51,6 +51,10 @@ object MBuiltInRegistries {
val ANDROID_FEATURE by Delegate(MRegistries.ANDROID_FEATURE) { sync(true) }
val STACK_TYPE by Delegate(MRegistries.STACK_TYPE)
val ITEM_FILTER by Delegate(MRegistries.ITEM_FILTER) {
defaultKey(ResourceLocation(OverdriveThatMatters.MOD_ID, "empty"))
}
internal fun register(bus: IEventBus) {
delegates.forEach { bus.addListener(it::build) }
}

View File

@ -3,6 +3,7 @@ package ru.dbotthepony.mc.otm.registry
import net.minecraft.core.Registry
import net.minecraft.resources.ResourceKey
import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.container.ItemFilter
import ru.dbotthepony.mc.otm.core.ResourceLocation
import ru.dbotthepony.mc.otm.data.world.DecimalProvider
import ru.dbotthepony.mc.otm.matter.AbstractRegistryAction
@ -26,4 +27,5 @@ object MRegistries {
val ANDROID_RESEARCH_RESULT = k<AndroidResearchResult.Type<*>>("android_research_result")
val ANDROID_FEATURE = k<AndroidFeatureType<*>>("android_feature")
val STACK_TYPE = k<StorageStack.Type<*>>("stack_type")
val ITEM_FILTER = k<ItemFilter.Type<*>>("item_filter")
}

View File

@ -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<ImmutableList<PatternState>>().persistent(Codec.list(PatternState.CODEC).xmap({ ImmutableList.copyOf(it) }, { it })).build()
}
val ITEM_FILTER: DataComponentType<ItemFilter> by registry.register("item_filter") { DataComponentType.builder<ItemFilter>().persistent(ItemFilter.CODEC).build() }
val ITEM_FILTER: DataComponentType<ItemFilterSet> by registry.register("item_filter") { DataComponentType.builder<ItemFilterSet>().persistent(ItemFilterSet.CODEC).build() }
val TICK_TIMER: DataComponentType<RedstoneInteractorItem.TickTimer> by registry.register("tick_timer") { DataComponentType.builder<RedstoneInteractorItem.TickTimer>().persistent(RedstoneInteractorItem.TickTimer.CODEC).build() }
val EXPERIENCE: DataComponentType<Long> by registry.register("experience") { DataComponentType.builder<Long>().persistent(Codec.LONG).networkSynchronized(StreamCodecs.LONG).build() }