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.MachinesConfig
import ru.dbotthepony.mc.otm.config.ServerConfig import ru.dbotthepony.mc.otm.config.ServerConfig
import ru.dbotthepony.mc.otm.config.ToolsConfig 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.FlywheelMaterials
import ru.dbotthepony.mc.otm.data.world.DecimalProvider import ru.dbotthepony.mc.otm.data.world.DecimalProvider
import ru.dbotthepony.mc.otm.entity.WitheredSkeletonSpawnHandler import ru.dbotthepony.mc.otm.entity.WitheredSkeletonSpawnHandler
@ -141,6 +142,7 @@ object OverdriveThatMatters {
AbstractRegistryAction.register(MOD_BUS) AbstractRegistryAction.register(MOD_BUS)
IMatterFunction.register(MOD_BUS) IMatterFunction.register(MOD_BUS)
ItemFilter.register(MOD_BUS)
MRegistry.initialize(MOD_BUS) MRegistry.initialize(MOD_BUS)
MatterManager.initialize(MOD_BUS) MatterManager.initialize(MOD_BUS)

View File

@ -1,11 +1,7 @@
package ru.dbotthepony.mc.otm.block.entity.matter 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.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.Util
import net.minecraft.core.BlockPos import net.minecraft.core.BlockPos
import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player 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.map
import ru.dbotthepony.mc.otm.core.collect.toList import ru.dbotthepony.mc.otm.core.collect.toList
import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.util.ItemStackKey import ru.dbotthepony.mc.otm.container.ItemStackKey
import ru.dbotthepony.mc.otm.core.util.asKey import ru.dbotthepony.mc.otm.container.asKey
import ru.dbotthepony.mc.otm.data.codec.DecimalCodec import ru.dbotthepony.mc.otm.data.codec.DecimalCodec
import ru.dbotthepony.mc.otm.data.codec.minRange import ru.dbotthepony.mc.otm.data.codec.minRange
import ru.dbotthepony.mc.otm.graph.matter.MatterNode 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.ProfiledEnergyStorage
import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage
import ru.dbotthepony.mc.otm.config.MachinesConfig 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.*
import ru.dbotthepony.mc.otm.core.math.RelativeSide import ru.dbotthepony.mc.otm.core.math.RelativeSide
import ru.dbotthepony.mc.otm.core.math.isPositive 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) { set(value) {
field = value field = value
component?.scan() component?.scan()
@ -131,7 +131,7 @@ class StorageBusBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matter
} }
init { 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) { override fun setLevel(level: Level) {
@ -348,7 +348,7 @@ class StorageBusBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matter
} }
fun scan(slot: Int) { 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] val last = slot2itemStack[slot]
if (current == null && last != null) { if (current == null && last != null) {
@ -374,7 +374,7 @@ class StorageBusBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matter
} }
override fun insertStack(stack: ItemStorageStack, simulate: Boolean): ItemStorageStack { 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 return stack
val required = StorageStack.ITEMS.energyPerInsert(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.capability.energy.WorkerEnergyStorage
import ru.dbotthepony.mc.otm.config.EnergyBalanceValues import ru.dbotthepony.mc.otm.config.EnergyBalanceValues
import ru.dbotthepony.mc.otm.config.MachinesConfig 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.TranslatableComponent
import ru.dbotthepony.mc.otm.core.isNotEmpty import ru.dbotthepony.mc.otm.core.isNotEmpty
import ru.dbotthepony.mc.otm.core.math.RelativeSide import ru.dbotthepony.mc.otm.core.math.RelativeSide
@ -98,7 +98,7 @@ abstract class AbstractStorageImportExport(
protected val target = CapabilityCache(RelativeSide.FRONT, Capabilities.ItemHandler.BLOCK) protected val target = CapabilityCache(RelativeSide.FRONT, Capabilities.ItemHandler.BLOCK)
var filter: ItemFilter = ItemFilter(MAX_FILTERS) var filter: ItemFilterSet = ItemFilterSet.EMPTY
set(value) { set(value) {
if (value != field) { if (value != field) {
field = value field = value
@ -112,7 +112,7 @@ abstract class AbstractStorageImportExport(
} }
init { init {
savetablesConfig.codec(::filter, ItemFilter.CODEC, FILTER_KEY, Supplier { ItemFilter(MAX_FILTERS) }) savetablesConfig.codec(::filter, ItemFilterSet.CODEC, FILTER_KEY, Supplier { ItemFilterSet.EMPTY })
} }
companion object { companion object {
@ -168,7 +168,7 @@ class StorageImporterBlockEntity(
} }
override fun insertItem(slot: Int, stack: ItemStack, simulate: Boolean): ItemStack { 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 stack
return acceptItem(stack, simulate) return acceptItem(stack, simulate)
@ -183,7 +183,7 @@ class StorageImporterBlockEntity(
} }
override fun isItemValid(slot: Int, stack: ItemStack): Boolean { override fun isItemValid(slot: Int, stack: ItemStack): Boolean {
return filter.match(stack) return filter.test(stack)
} }
override fun tick() { override fun tick() {
@ -205,7 +205,7 @@ class StorageImporterBlockEntity(
val extracted = target.extractItem(lastSlot, MAX_MOVE_PER_OPERATION, true) val extracted = target.extractItem(lastSlot, MAX_MOVE_PER_OPERATION, true)
if (extracted.isEmpty || !filter.match(extracted)) { if (extracted.isEmpty || !filter.test(extracted)) {
lastSlot++ lastSlot++
} else { } else {
val leftover = acceptItem(extracted, true) 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>) { override fun onStackAdded(stack: ItemStorageStack, id: UUID, provider: IStorageProvider<ItemStorageStack>) {
if (filter.match(stack.toItemStack())) { if (filter.test(stack.toItemStack())) {
relevantTuples.add(id) 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.SimpleCache
import ru.dbotthepony.mc.otm.core.immutableList import ru.dbotthepony.mc.otm.core.immutableList
import ru.dbotthepony.mc.otm.core.otmRandom import ru.dbotthepony.mc.otm.core.otmRandom
import ru.dbotthepony.mc.otm.core.util.ItemStackKey import ru.dbotthepony.mc.otm.container.ItemStackKey
import ru.dbotthepony.mc.otm.core.util.asKey import ru.dbotthepony.mc.otm.container.asKey
import ru.dbotthepony.mc.otm.menu.tech.PoweredFurnaceMenu import ru.dbotthepony.mc.otm.menu.tech.PoweredFurnaceMenu
import ru.dbotthepony.mc.otm.recipe.MatteryCookingRecipe import ru.dbotthepony.mc.otm.recipe.MatteryCookingRecipe
import ru.dbotthepony.mc.otm.recipe.MicrowaveRecipe 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.FilteredContainerSlot
import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer
import ru.dbotthepony.mc.otm.core.SimpleCache 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.otmRandom
import ru.dbotthepony.mc.otm.core.util.ItemStackKey import ru.dbotthepony.mc.otm.container.ItemStackKey
import ru.dbotthepony.mc.otm.core.util.asKey import ru.dbotthepony.mc.otm.container.asKey
import ru.dbotthepony.mc.otm.menu.tech.PlatePressMenu import ru.dbotthepony.mc.otm.menu.tech.PlatePressMenu
import ru.dbotthepony.mc.otm.registry.game.MBlockEntities import ru.dbotthepony.mc.otm.registry.game.MBlockEntities
import ru.dbotthepony.mc.otm.registry.game.MRecipes 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 matteryPlayer = matteryPlayer
val iterators = ArrayList<Iterator<ItemStack>>() 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) { 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.exopackEnergy.parent.iterator())
iterators.add(matteryPlayer.exopackChargeSlots.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>>() val streams = ArrayList<Stream<out AwareItemStack>>()
streams.add(inventory.awareStream()) streams.add(inventory.awareStream())
matteryPlayer?.let { if (matteryPlayer.hasExopack) {
if (it.hasExopack) { streams.add(matteryPlayer.exopackContainer.awareStream())
streams.add(it.exopackContainer.awareStream())
}
} }
if (isCuriosLoaded) { if (isCuriosLoaded) {

View File

@ -4,22 +4,36 @@ import net.minecraft.world.item.ItemStack
import ru.dbotthepony.kommons.util.Delegate import ru.dbotthepony.kommons.util.Delegate
import ru.dbotthepony.mc.otm.client.screen.MatteryScreen import ru.dbotthepony.mc.otm.client.screen.MatteryScreen
import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel 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, screen: S,
parent: EditablePanel<*>?, parent: EditablePanel<*>?,
val slot: Delegate<ItemStack>, val slot: Delegate<ItemFilter>,
x: Float = 0f, x: Float = 0f,
y: Float = 0f, y: Float = 0f,
width: Float = SIZE, width: Float = SIZE,
height: Float = SIZE height: Float = SIZE
) : AbstractSlotPanel<S>(screen, parent, x, y, width, height) { ) : AbstractSlotPanel<S>(screen, parent, x, y, width, height) {
private var lastFilteredItemDisplayUpdate = System.nanoTime()
private var filteredItemDisplayIndex = 0
override val itemStack: ItemStack get() { 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 { override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean {
slot.accept(screen.menu.carried) slot.accept(ItemFilter.item(screen.menu.carried))
return true 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.isItemBordersLoaded
import ru.dbotthepony.mc.otm.compat.itemborders.renderSlotBorder import ru.dbotthepony.mc.otm.compat.itemborders.renderSlotBorder
import ru.dbotthepony.mc.otm.container.IFilteredContainerSlot 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.container.util.containerSlotOrNull
import ru.dbotthepony.mc.otm.core.TextComponent import ru.dbotthepony.mc.otm.core.TextComponent
import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.isNotEmpty
import ru.dbotthepony.mc.otm.menu.UserFilteredMenuSlot import ru.dbotthepony.mc.otm.menu.UserFilteredMenuSlot
import javax.annotation.Nonnull import javax.annotation.Nonnull
import kotlin.math.roundToInt 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) {} 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) { override fun renderSlotBackground(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) {
super.renderSlotBackground(graphics, mouseX, mouseY, partialTick) super.renderSlotBackground(graphics, mouseX, mouseY, partialTick)
@ -71,16 +92,16 @@ open class SlotPanel<out S : MatteryScreen<*>, out T : Slot>(
if (containerSlot is IFilteredContainerSlot) { if (containerSlot is IFilteredContainerSlot) {
renderBackgroundBeforeFilter(graphics, mouseX, mouseY, partialTick) renderBackgroundBeforeFilter(graphics, mouseX, mouseY, partialTick)
if (containerSlot.filter !== null) { if (containerSlot.filter.denyAll) {
if (containerSlot.filter !== Items.AIR) { graphics.renderRect(0f, 0f, width, height, color = SLOT_BLOCK_COLOR)
val itemStack = ItemStack(containerSlot.filter!!, 1) } else if (!containerSlot.filter.allowAll) {
val itemStack = selectRandomItemFromFilter(containerSlot.filter)
if (itemStack.isNotEmpty) {
screen.renderItemStack(graphics, itemStack, null) screen.renderItemStack(graphics, itemStack, null)
clearDepth(graphics) clearDepth(graphics)
graphics.renderRect(0f, 0f, width, height, color = SLOT_FILTER_COLOR) 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 val slot = slot.container.containerSlotOrNull(slot.containerSlot) as? IFilteredContainerSlot
if (isHovered && slot?.filter != null && slot.filter !== Items.AIR && itemStack.isEmpty) { if (isHovered && slot?.filter != null && slot.filter.hasRules && itemStack.isEmpty) {
val itemstack = ItemStack(slot.filter!!, 1) val itemstack = selectRandomItemFromFilter(slot.filter)
graphics.renderComponentTooltip( val text: List<Component>
IClientItemExtensions.of(itemstack).getFont(itemstack, IClientItemExtensions.FontContext.TOOLTIP) ?: font,
getItemStackTooltip(itemstack).toMutableList().also { 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(0, TranslatableComponent("otm.gui.slot_filter.filtered").withStyle(ChatFormatting.GRAY))
it.add(1, TranslatableComponent("otm.gui.slot_filter.hint").withStyle(ChatFormatting.GRAY)) it.add(1, TranslatableComponent("otm.gui.slot_filter.hint").withStyle(ChatFormatting.GRAY))
it.add(2, TextComponent("")) it.add(2, TextComponent(""))
}, }
}
graphics.renderComponentTooltip(
IClientItemExtensions.of(itemstack).getFont(itemstack, IClientItemExtensions.FontContext.TOOLTIP) ?: font,
text,
mouseX.toInt(), mouseX.toInt(),
mouseY.toInt(), mouseY.toInt(),
itemstack itemstack
) )
return true return true
} else if (isHovered && slot?.filter === Items.AIR && itemStack.isEmpty) { } else if (isHovered && slot?.filter?.denyAll == true && itemStack.isEmpty) {
graphics.renderComponentTooltip( graphics.renderComponentTooltip(
font, font,
ArrayList<Component>().also { 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.MatteryScreen
import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel
import ru.dbotthepony.mc.otm.container.IFilteredContainerSlot 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.container.util.containerSlot
import ru.dbotthepony.mc.otm.menu.UserFilteredMenuSlot import ru.dbotthepony.mc.otm.menu.UserFilteredMenuSlot
@ -20,20 +21,18 @@ open class UserFilteredSlotPanel<out S : MatteryScreen<*>, out T : UserFilteredM
height: Float = SIZE, height: Float = SIZE,
) : SlotPanel<S, T>(screen, parent, slot, x, y, width, height) { ) : SlotPanel<S, T>(screen, parent, slot, x, y, width, height) {
override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean { override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean {
if (slot.filterInput == null) val filterInput = slot.filterInput ?: return super.mouseClickedInner(x, y, button)
return super.mouseClickedInner(x, y, button)
val containerSlot = slot.containerSlot() as IFilteredContainerSlot val containerSlot = slot.containerSlot() as IFilteredContainerSlot
if (button == InputConstants.MOUSE_BUTTON_LEFT && minecraft.window.isCtrlDown) { if (button == InputConstants.MOUSE_BUTTON_LEFT && minecraft.window.isCtrlDown) {
if (containerSlot.filter === null) { if (containerSlot.filter.allowAll) {
if (screen.menu.carried.isEmpty) { if (screen.menu.carried.isEmpty) {
slot.filterInput!!.accept(slot.item.item) filterInput.accept(ItemFilter.item(slot.item.item))
} else { } else {
slot.filterInput!!.accept(screen.menu.carried.item) filterInput.accept(ItemFilter.item(screen.menu.carried.item))
} }
} else { } else {
slot.filterInput!!.accept(null) filterInput.accept(ItemFilter.EMPTY)
} }
playGuiClickSound() playGuiClickSound()

View File

@ -66,14 +66,12 @@ class DriveViewerScreen(menu: DriveViewerMenu, inventory: Inventory, title: Comp
settings.add(filterGrid) settings.add(filterGrid)
for (i in 0 until PortableCondensationDriveItem.MAX_FILTERS) { 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 { settings.add(EditablePanel(this, frame, width = 90f).also {
it.dock = Dock.LEFT 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.driveFilter.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 }
}) })
frame.CustomTab(view, activeIcon = ItemStackIcon(ItemStack(MItems.PORTABLE_CONDENSATION_DRIVE))) frame.CustomTab(view, activeIcon = ItemStackIcon(ItemStack(MItems.PORTABLE_CONDENSATION_DRIVE)))

View File

@ -34,18 +34,6 @@ class StorageImporterExporterScreen(menu: StorageImporterExporterMenu, inventory
it.childrenOrder = -1 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) makeDeviceControls(this, frame, redstoneConfig = menu.redstoneConfig, energyConfig = menu.energyConfig)
return frame return frame

View File

@ -241,9 +241,9 @@ fun Container.sortWithIndices(sortedSlots: IntCollection) {
if (slot is IFilteredContainerSlot) { if (slot is IFilteredContainerSlot) {
condition = slot.isNotEmpty && condition = slot.isNotEmpty &&
!slot.isForbiddenForAutomation && !slot.filter.denyAll &&
slot.item.count <= slot.maxStackSize(slot.item) && 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 { } else {
condition = slot.isNotEmpty && slot.item.count <= slot.maxStackSize(slot.item) condition = slot.isNotEmpty && slot.item.count <= slot.maxStackSize(slot.item)
} }
@ -268,7 +268,7 @@ fun Container.computeSortedIndices(comparator: Comparator<ItemStack> = ItemStack
val slots = slotIterator() val slots = slotIterator()
.withIndex() .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() .toList()
if (slots.isEmpty()) if (slots.isEmpty())

View File

@ -282,9 +282,9 @@ interface IEnhancedContainer<out S : IContainerSlot> : Container, RecipeInput, I
val condition: Boolean val condition: Boolean
if (slot is IFilteredContainerSlot) { if (slot is IFilteredContainerSlot) {
condition = (ignoreFilters || !slot.isForbiddenForAutomation) && condition = (ignoreFilters || !slot.filter.denyAll) &&
ItemStack.isSameItemSameComponents(slot.item, stack) && 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 { } else {
condition = (ignoreFilters || !filterPass) && ItemStack.isSameItemSameComponents(slot.item, stack) condition = (ignoreFilters || !filterPass) && ItemStack.isSameItemSameComponents(slot.item, stack)
} }
@ -318,8 +318,8 @@ interface IEnhancedContainer<out S : IContainerSlot> : Container, RecipeInput, I
val condition: Boolean val condition: Boolean
if (slot is IFilteredContainerSlot) { if (slot is IFilteredContainerSlot) {
condition = (ignoreFilters || !slot.isForbiddenForAutomation) && condition = (ignoreFilters || !slot.filter.denyAll) &&
(ignoreFilters || !filterPass && !slot.hasFilter || filterPass && slot.hasFilter && slot.testSlotFilter(stack)) (ignoreFilters || !filterPass && slot.filter.allowAll || filterPass && !slot.filter.allowAll && slot.filter.test(stack))
} else { } else {
condition = ignoreFilters || !filterPass condition = ignoreFilters || !filterPass
} }

View File

@ -5,10 +5,10 @@ import net.minecraft.world.item.Items
interface IFilteredAutomatedContainerSlot : IFilteredContainerSlot, IAutomatedContainerSlot { interface IFilteredAutomatedContainerSlot : IFilteredContainerSlot, IAutomatedContainerSlot {
override fun canAutomationPlaceItem(itemStack: ItemStack): Boolean { 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 { 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 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 { interface IFilteredContainerSlot : IContainerSlot {
var filter: Item? var filter: ItemFilter
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
}
}
} }

View File

@ -1,124 +1,205 @@
package ru.dbotthepony.mc.otm.container 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.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder import com.mojang.serialization.DataResult
import it.unimi.dsi.fastutil.objects.ObjectArrayList 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.tags.TagKey
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack 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) { interface ItemFilter : Predicate<ItemStack> {
constructor(size: Int, isWhitelist: Boolean = false, matchTag: Boolean = false, matchComponents: Boolean = false) : this(Array(size) { ItemStack.EMPTY }, isWhitelist, matchTag, matchComponents) interface Type<T : ItemFilter> {
constructor(list: List<ItemStack>, isWhitelist: Boolean = false, matchTag: Boolean = false, matchComponents: Boolean = false) : this(list.toTypedArray(), isWhitelist, matchTag, matchComponents) val codec: MapCodec<T>
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
} }
override fun hashCode(): Int { val type: Type<*>
return filter.contentHashCode()
/**
* 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 private data class Tag(val tag: TagKey<net.minecraft.world.item.Item>) : ItemFilter {
get() = filter.size override val type: Type<*>
get() = Companion
fun set(index: Int, value: ItemStack): ItemFilter { override fun test(t: ItemStack): Boolean {
if (ItemStack.isSameItemSameComponents(filter[index], value) || !value.isEmpty && filter.any { ItemStack.isSameItemSameComponents(it, value) }) return t.`is`(tag)
return this }
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 { private object DenyAll : ItemFilter, Type<DenyAll> {
return filter[index] override val type: Type<*>
} get() = this
private fun copy( override val codec: MapCodec<DenyAll> = MapCodec.unit(this)
filter: Array<ItemStack> = this.filter,
isWhitelist: Boolean = this.isWhitelist,
matchTag: Boolean = this.matchTag,
matchComponents: Boolean = this.matchComponents,
) = ItemFilter(filter, isWhitelist, matchTag, matchComponents)
fun isWhitelist(flag: Boolean): ItemFilter { override val denyAll: Boolean
if (flag == isWhitelist) get() = true
return this
else
return copy(isWhitelist = flag)
}
fun matchTag(flag: Boolean): ItemFilter { override fun test(t: ItemStack): Boolean {
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) {
return false 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 { companion object : ItemFilter, Type<Companion> {
val EMPTY = ItemFilter(0) 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 { val CODEC: Codec<ItemFilter> by lazy {
RecordCodecBuilder.create<ItemFilter> { val codecA = BuiltInRegistries.ITEM
it.group( .byNameCodec()
Codec.list(ItemStack.OPTIONAL_CODEC, 0, 40).fieldOf("filter").forGetter { ObjectArrayList.wrap(it.filter) }, .xmap(::Item, Item::item)
Codec.BOOL.optionalFieldOf("isWhitelist", false).forGetter { it.isWhitelist },
Codec.BOOL.optionalFieldOf("matchTag", false).forGetter { it.matchTag }, val codecB = MBuiltInRegistries.ITEM_FILTER
Codec.BOOL.optionalFieldOf("matchComponents", false).forGetter { it.matchComponents }, .byNameCodec()
).apply(it, ::ItemFilter) .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 it.unimi.dsi.fastutil.HashCommon
import net.minecraft.core.component.DataComponentMap
import net.minecraft.core.component.DataComponentPatch import net.minecraft.core.component.DataComponentPatch
import net.minecraft.core.component.PatchedDataComponentMap
import net.minecraft.core.registries.BuiltInRegistries import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.world.item.Item import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import ru.dbotthepony.mc.otm.core.getHolder 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 // 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(itemStack: ItemStack) : this(itemStack.item, itemStack.copy().componentsPatch)
constructor(item: Item) : this(item, DataComponentPatch.EMPTY) constructor(item: Item) : this(item, DataComponentPatch.EMPTY)
@ -37,6 +40,33 @@ class ItemStackKey(val item: Item, val components: DataComponentPatch) {
override fun toString(): String { override fun toString(): String {
return "ItemStackKey[$item, $components]" 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 { 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.HolderLookup
import net.minecraft.core.registries.BuiltInRegistries import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.NbtOps
import net.minecraft.resources.ResourceLocation import net.minecraft.resources.ResourceLocation
import net.minecraft.world.item.Item import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack 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.IFilteredAutomatedContainerSlot
import ru.dbotthepony.mc.otm.container.ItemFilter
import ru.dbotthepony.mc.otm.core.nbt.set import ru.dbotthepony.mc.otm.core.nbt.set
import ru.dbotthepony.mc.otm.core.registryName import ru.dbotthepony.mc.otm.core.registryName
import java.util.Collections import java.util.Collections
@ -15,7 +18,7 @@ open class FilteredContainerSlot(
container: SlottedContainer, container: SlottedContainer,
slot: Int slot: Int
) : ContainerSlot(container, slot), IFilteredAutomatedContainerSlot { ) : ContainerSlot(container, slot), IFilteredAutomatedContainerSlot {
override var filter: Item? = null override var filter: ItemFilter = ItemFilter.EMPTY
set(value) { set(value) {
if (field !== value) { if (field !== value) {
field = value field = value
@ -25,21 +28,25 @@ open class FilteredContainerSlot(
override fun clear() { override fun clear() {
super.clear() super.clear()
filter = null filter = ItemFilter.EMPTY
} }
override fun serializeNBT(provider: HolderLookup.Provider): CompoundTag { override fun serializeNBT(provider: HolderLookup.Provider): CompoundTag {
return super.serializeNBT(provider).also { return super.serializeNBT(provider).also {
if (filter != null) it["filter"] = ItemFilter.CODEC.encodeStart(provider.createSerializationContext(NbtOps.INSTANCE), filter)
it["filter"] = filter!!.registryName!!.toString() .getOrThrow { RuntimeException("Failed to serialize item filter: $it") }
} }
} }
override fun deserializeNBT(provider: HolderLookup.Provider, nbt: CompoundTag) { override fun deserializeNBT(provider: HolderLookup.Provider, nbt: CompoundTag) {
super.deserializeNBT(provider, nbt) super.deserializeNBT(provider, nbt)
filter = ItemFilter.EMPTY
if ("filter" in nbt) { 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) 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.IAutomatedContainer
import ru.dbotthepony.mc.otm.container.IAutomatedContainerSlot import ru.dbotthepony.mc.otm.container.IAutomatedContainerSlot
import ru.dbotthepony.mc.otm.container.IFilteredContainerSlot 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.container.balance
import ru.dbotthepony.mc.otm.core.isNotEmpty import ru.dbotthepony.mc.otm.core.isNotEmpty
import ru.dbotthepony.mc.otm.core.nbt.set import ru.dbotthepony.mc.otm.core.nbt.set
@ -293,7 +294,7 @@ class SlottedContainer(
val getSlot = slots[slot] val getSlot = slots[slot]
if (getSlot is IFilteredContainerSlot) { 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.MatteryCapability
import ru.dbotthepony.mc.otm.capability.drive.DrivePool import ru.dbotthepony.mc.otm.capability.drive.DrivePool
import ru.dbotthepony.mc.otm.capability.drive.ItemMatteryDrive 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.core.TextComponent
import ru.dbotthepony.mc.otm.isServerThread import ru.dbotthepony.mc.otm.isServerThread
import ru.dbotthepony.mc.otm.registry.CapabilitiesRegisterListener import ru.dbotthepony.mc.otm.registry.CapabilitiesRegisterListener
@ -55,18 +55,18 @@ class PortableCondensationDriveItem(capacity: Int) : Item(Properties().stacksTo(
}, this) }, this)
} }
fun getFilterSettings(item: ItemStack): ItemFilter { fun getFilterSettings(item: ItemStack): ItemFilterSet {
return item.getOrDefault(MDataComponentTypes.ITEM_FILTER, EMPTY_FILTER) 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) item.set(MDataComponentTypes.ITEM_FILTER, filter)
} }
@Suppress("unused") @Suppress("unused")
companion object { companion object {
const val MAX_FILTERS = 4 * 3 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) { internal fun onPickupEvent(event: ItemEntityPickupEvent.Pre) {
if (event.itemEntity.owner != null && event.itemEntity.owner != event.player && event.itemEntity.age < 200 || event.itemEntity.item.isEmpty) { 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 var doBreak = false
stack.getCapability(MatteryCapability.CONDENSATION_DRIVE)?.let { 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 copy = event.itemEntity.item.copy()
val remaining = (it as ItemMatteryDrive).insertStack(event.itemEntity.item, false) 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.container.util.containerSlotOrNull
import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.isNotEmpty import ru.dbotthepony.mc.otm.core.isNotEmpty
import ru.dbotthepony.mc.otm.core.util.ItemStackKey import ru.dbotthepony.mc.otm.container.ItemStackKey
import ru.dbotthepony.mc.otm.core.util.asKey import ru.dbotthepony.mc.otm.container.asKey
import ru.dbotthepony.mc.otm.core.util.asKeyOrNull 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) { 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 slotA = a.containerSlotOrNull()
val slotB = b.containerSlotOrNull() val slotB = b.containerSlotOrNull()
val hasFilterA = slotA is IFilteredContainerSlot && slotA.hasFilter val hasFilterA = slotA is IFilteredContainerSlot && slotA.filter.hasRules
val hasFilterB = slotB is IFilteredContainerSlot && slotB.hasFilter val hasFilterB = slotB is IFilteredContainerSlot && slotB.filter.hasRules
return hasFilterB.compareTo(hasFilterA) 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) { override fun move(from: Collection<Slot>, to: Collection<Slot>, player: Player, dontTouchFilteredSlots: Boolean) {
if (from.isEmpty() || to.isEmpty()) return if (from.isEmpty() || to.isEmpty()) return
val (_, itemsFrom) = computeSlotLists(from, dontTouchFilteredSlots) 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) { for (key in intersect) {
val slotsTo = itemsTo[key]!! val slotsTo = ArrayList(itemsTo[key] ?: listOf())
slotsTo.addAll(0, filteredTo)
val slotsFrom = itemsFrom[key]!! val slotsFrom = itemsFrom[key]!!
if (!dontTouchFilteredSlots) {
// touch filtered slots last
slotsFrom.sortWith(HasFilterComparator.reversed())
}
slotsFrom.forEach { moveItemStackTo(player, it, slotsTo) } 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) { override fun move(from: Collection<Slot>, to: Collection<Slot>, player: Player, dontTouchFilteredSlots: Boolean) {
if (from.isEmpty() || to.isEmpty()) return if (from.isEmpty() || to.isEmpty()) return
val (_, itemsFrom) = computeSlotLists(from, dontTouchFilteredSlots) 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) { for (key in intersect) {
val slotsTo = prioritySortSlots(itemsTo[key]!!, key.asItemStack()) val slotsTo = ArrayList(itemsTo[key] ?: listOf()).also { it.addAll(0, filteredTo) }
val slotsFrom = itemsFrom[key]!! if (slotsTo.isEmpty()) continue
prioritySortSlotsInPlace(slotsTo, key.asItemStack())
if (!dontTouchFilteredSlots) { val slotsFrom = itemsFrom[key]!!
// touch filtered slots last
slotsFrom.sortWith(HasFilterComparator.reversed())
}
slotsFrom.removeIf { moveItemStackTo(player, it, slotsTo, sort = false); it.item.isEmpty } slotsFrom.removeIf { moveItemStackTo(player, it, slotsTo, sort = false); it.item.isEmpty }
var moveAny = false var moveAny = false
@ -108,7 +116,7 @@ class QuickMoveInput(private val menu: MatteryMenu, val from: Collection<Slot>,
from.forEach { from.forEach {
val slot = it.containerSlotOrNull() 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) 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) 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 { companion object {
fun create(menu: MatteryMenu, from: Collection<Slot>, to: Collection<Slot>, dontTouchFilteredSlots: Boolean = true): Map<Mode, QuickMoveInput> { 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) } 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 emptySlots = ArrayList<Slot>()
val filteredSlots = ArrayList<Slot>()
val filledSlots = HashMap<ItemStackKey, MutableList<Slot>>() val filledSlots = HashMap<ItemStackKey, MutableList<Slot>>()
for (slot in slots) { for (slot in slots) {
val underlyingSlot = slot.containerSlotOrNull() 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 continue
val key = slot.item.asKeyOrNull() ?: (underlyingSlot as? IFilteredContainerSlot)?.filter?.asKey() val key = slot.item.asKeyOrNull()
if (key == null) { if (key == null) {
emptySlots.add(slot) if (underlyingSlot is IFilteredContainerSlot && underlyingSlot.filter.hasRules) {
filteredSlots.add(slot)
} else {
emptySlots.add(slot)
}
} else { } else {
filledSlots.computeIfAbsent(key) { ArrayList() }.add(slot) filledSlots.computeIfAbsent(key) { ArrayList() }.add(slot)
} }
} }
return emptySlots to filledSlots return SlotLists(emptySlots, filledSlots, filteredSlots)
} }
fun moveItemStackTo( 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 { fun <T : MutableList<Slot>> prioritySortSlotsInPlace(slots: T, filterItem: ItemStack? = null): T {
slots.removeIf { slots.removeIf {
val slot = it.containerSlotOrNull() 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) 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.inventory.Slot
import net.minecraft.world.item.Item import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack import net.minecraft.world.item.ItemStack
import net.neoforged.neoforge.capabilities.Capabilities
import ru.dbotthepony.kommons.util.Delegate import ru.dbotthepony.kommons.util.Delegate
import ru.dbotthepony.kommons.util.getValue import ru.dbotthepony.kommons.util.getValue
import ru.dbotthepony.kommons.util.value 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.UpgradeType
import ru.dbotthepony.mc.otm.capability.energy import ru.dbotthepony.mc.otm.capability.energy
import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage 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.EnhancedContainer
import ru.dbotthepony.mc.otm.container.IEnhancedContainer import ru.dbotthepony.mc.otm.container.IEnhancedContainer
import ru.dbotthepony.mc.otm.container.IFilteredContainerSlot import ru.dbotthepony.mc.otm.container.IFilteredContainerSlot
import ru.dbotthepony.mc.otm.container.ItemFilter 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.UpgradeContainer
import ru.dbotthepony.mc.otm.container.util.containerSlotOrNull import ru.dbotthepony.mc.otm.container.util.containerSlotOrNull
import ru.dbotthepony.mc.otm.core.collect.ConditionalEnumSet 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.core.math.Decimal
import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback
import ru.dbotthepony.mc.otm.menu.input.InstantBooleanInput 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.network.StreamCodecs
import ru.dbotthepony.mc.otm.player.IPlayerInventorySlot import ru.dbotthepony.mc.otm.player.IPlayerInventorySlot
import ru.dbotthepony.mc.otm.runOnClient
import java.util.* import java.util.*
import java.util.function.BooleanSupplier import java.util.function.BooleanSupplier
import java.util.function.DoubleSupplier import java.util.function.DoubleSupplier
@ -61,7 +60,7 @@ open class MatteryMenuSlot(container: Container, index: Int, x: Int = 0, y: Int
val slot = containerSlotOrNull() val slot = containerSlotOrNull()
if (slot is IFilteredContainerSlot && slot !is IPlayerInventorySlot) { 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) { 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 private set
override fun setupNetworkControls(menu: MatteryMenu) { override fun setupNetworkControls(menu: MatteryMenu) {
@ -121,7 +120,7 @@ open class UserFilteredMenuSlot(container: Container, index: Int, x: Int = 0, y:
val slot = containerSlotOrNull() val slot = containerSlotOrNull()
if (slot is IFilteredContainerSlot) { 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>> { fun MatteryMenu.addFilterSlots(amount: Int, slots: Delegate<ItemFilterSet>?): ItemFilterInput {
val result = ArrayList<Delegate<ItemStack>>(slots.value.size) return ItemFilterInput(this, amount, slots)
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): List<Delegate<ItemStack>> { fun MatteryMenu.addFilterSlots(amount: Int): ItemFilterInput {
val result = ArrayList<Delegate<ItemStack>>(amount) return addFilterSlots(amount, Delegate.Box(ItemFilterSet.EMPTY))
for (i in 0 until amount) {
result.add(Delegate.Of(
mSynchronizer.computedItem { ItemStack.EMPTY },
itemStackInput { throw UnsupportedOperationException() }
))
}
return result
} }
fun MatteryMenu.addFilterSlots(slots: Delegate<ItemFilter>?, amount: Int): List<Delegate<ItemStack>> { fun MatteryMenu.addFilterSlots(amount: Int, slots: KMutableProperty0<ItemFilterSet>?): ItemFilterInput {
if (slots != null && amount != slots.value.size) return addFilterSlots(amount, if (slots == null) null else Delegate.Of(slots))
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)
} }
val Slot.isOverCapacity: Boolean get() { 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 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.Inventory
import net.minecraft.world.entity.player.Player import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack 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.capability.energy.ProfiledEnergyStorage
import ru.dbotthepony.mc.otm.container.EnhancedContainer import ru.dbotthepony.mc.otm.container.EnhancedContainer
import ru.dbotthepony.mc.otm.container.ItemFilter 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.immutableList
import ru.dbotthepony.mc.otm.core.util.ItemStorageStackSorter import ru.dbotthepony.mc.otm.core.util.ItemStorageStackSorter
import ru.dbotthepony.mc.otm.item.PortableCondensationDriveItem 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.BooleanInputWithFeedback
import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput
import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback 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.menu.widget.ProfiledLevelGaugeWidget
import ru.dbotthepony.mc.otm.network.StreamCodecs
import ru.dbotthepony.mc.otm.registry.game.MMenus import ru.dbotthepony.mc.otm.registry.game.MMenus
import ru.dbotthepony.mc.otm.storage.ItemStorageStack import ru.dbotthepony.mc.otm.storage.ItemStorageStack
import ru.dbotthepony.mc.otm.storage.StorageStack import ru.dbotthepony.mc.otm.storage.StorageStack
@ -75,26 +77,21 @@ class DriveViewerMenu(
var drivePresent by mSynchronizer.boolean() var drivePresent by mSynchronizer.boolean()
private fun getFilter(): ItemFilter? { private fun getFilter(): ItemFilterSet {
val stack = (tile as? DriveViewerBlockEntity)?.container?.getItem(0) 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) val stack = (tile as? DriveViewerBlockEntity)?.container?.getItem(0)
(stack?.item as? PortableCondensationDriveItem)?.setFilterSettings(stack, value) (stack?.item as? PortableCondensationDriveItem)?.setFilterSettings(stack, value)
} }
val driveFilterSlots = immutableList(PortableCondensationDriveItem.MAX_FILTERS) { i -> val driveFilter = ItemFilterInput(this, PortableCondensationDriveItem.MAX_FILTERS, Delegate.Of(::getFilter, ::setFilter))
Delegate.Of(
mSynchronizer.computedItem { getFilter()?.get(i) ?: ItemStack.EMPTY },
itemStackInput { getFilter()?.set(i, it) }.filter { drivePresent }
)
}
val isWhitelist = BooleanInputWithFeedback.dispatch(this, Delegate.Of({ getFilter() }, { setFilter(it!!) }), { it?.isWhitelist ?: false }, { it, v -> it?.isWhitelist(v) }).also { it.filter { drivePresent } } init {
val matchTag = BooleanInputWithFeedback.dispatch(this, Delegate.Of({ getFilter() }, { setFilter(it!!) }), { it?.matchTag ?: false }, { it, v -> it?.matchTag(v) }).also { it.filter { drivePresent } } driveFilter.filter { drivePresent }
val matchComponents = BooleanInputWithFeedback.dispatch(this, Delegate.Of({ getFilter() }, { setFilter(it!!) }), { it?.matchComponents ?: false }, { it, v -> it?.matchComponents(v) }).also { it.filter { drivePresent } } }
override fun broadcastChanges() { override fun broadcastChanges() {
super.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.block.entity.storage.StorageBusBlockEntity
import ru.dbotthepony.mc.otm.capability.FlowDirection import ru.dbotthepony.mc.otm.capability.FlowDirection
import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu 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.EnergyConfigPlayerInput
import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback
import ru.dbotthepony.mc.otm.menu.input.IntInputWithFeedback import ru.dbotthepony.mc.otm.menu.input.IntInputWithFeedback
@ -16,7 +16,7 @@ class StorageBusMenu(
inventory: Inventory, inventory: Inventory,
tile: StorageBusBlockEntity? = null tile: StorageBusBlockEntity? = null
) : MatteryPoweredMenu(MMenus.STORAGE_BUS, containerId, inventory, tile) { ) : 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 insertPriority = IntInputWithFeedback(this, tile?.let { it::insertPriority })
val extractPriority = IntInputWithFeedback(this, tile?.let { it::extractPriority }) val extractPriority = IntInputWithFeedback(this, tile?.let { it::extractPriority })
val profiledEnergy = ProfiledLevelGaugeWidget(this, tile?.energy, energyWidget) 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 net.minecraft.world.entity.player.Inventory
import ru.dbotthepony.mc.otm.block.entity.storage.AbstractStorageImportExport import ru.dbotthepony.mc.otm.block.entity.storage.AbstractStorageImportExport
import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu 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.EnergyConfigPlayerInput
import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget
import ru.dbotthepony.mc.otm.registry.game.MMenus import ru.dbotthepony.mc.otm.registry.game.MMenus
@ -11,7 +11,7 @@ import ru.dbotthepony.mc.otm.registry.game.MMenus
class StorageImporterExporterMenu( class StorageImporterExporterMenu(
containerId: Int, inventory: Inventory, tile: AbstractStorageImportExport? = null containerId: Int, inventory: Inventory, tile: AbstractStorageImportExport? = null
) : MatteryPoweredMenu(MMenus.STORAGE_IMPORTER_EXPORTER, containerId, inventory, tile) { ) : 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 profiledEnergy = ProfiledLevelGaugeWidget(this, tile?.energy, energyWidget)
val energyConfig = EnergyConfigPlayerInput(this, tile?.energyConfig) val energyConfig = EnergyConfigPlayerInput(this, tile?.energyConfig)

View File

@ -484,7 +484,7 @@ class QuickStackPacket(
slots.forEach { slots.forEach {
val slot = it.containerSlotOrNull() val slot = it.containerSlotOrNull()
if (it.hasItem() || slot is IFilteredContainerSlot && slot.hasFilter) if (it.hasItem() || slot is IFilteredContainerSlot && !slot.filter.allowAll)
prioritySlots.add(it) 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.Block
import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.level.block.state.BlockState
import ru.dbotthepony.kommons.math.RGBAColor 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.readDecimal
import ru.dbotthepony.mc.otm.core.util.writeDecimal import ru.dbotthepony.mc.otm.core.util.writeDecimal
import ru.dbotthepony.mc.otm.core.readBlockType import ru.dbotthepony.mc.otm.core.readBlockType
@ -43,6 +44,8 @@ object StreamCodecs {
val ITEM_TYPE_NULLABLE = ITEM_TYPE.nullable() val ITEM_TYPE_NULLABLE = ITEM_TYPE.nullable()
val DECIMAL = StreamCodec.of(FriendlyByteBuf::writeDecimal, FriendlyByteBuf::readDecimal).wrap() 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( fun <S : ByteBuf, T, T0, T1, T2, T3, T4, T5, T6> composite(
c0: StreamCodec<in S, T0>, g0: (T) -> T0, c0: StreamCodec<in S, T0>, g0: (T) -> T0,
c1: StreamCodec<in S, T1>, g1: (T) -> T1, 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.EnhancedContainer
import ru.dbotthepony.mc.otm.container.IContainerSlot import ru.dbotthepony.mc.otm.container.IContainerSlot
import ru.dbotthepony.mc.otm.container.IEnhancedContainer import ru.dbotthepony.mc.otm.container.IEnhancedContainer
import ru.dbotthepony.mc.otm.container.ItemFilter
class ExopackContainer(size: Int, val player: MatteryPlayer) : EnhancedContainer<IPlayerInventorySlot>(size) { class ExopackContainer(size: Int, val player: MatteryPlayer) : EnhancedContainer<IPlayerInventorySlot>(size) {
private inner class Slot(slot: Int) : IContainerSlot.Simple(slot, this@ExopackContainer), IPlayerInventorySlot { 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 get() = (PlayerInventoryWrapper.SLOTS + slot) in player.slotsChargeFlag
set(value) { if (value) player.slotsChargeFlag.add(PlayerInventoryWrapper.SLOTS + slot) else player.slotsChargeFlag.remove(PlayerInventoryWrapper.SLOTS + slot) } set(value) { if (value) player.slotsChargeFlag.add(PlayerInventoryWrapper.SLOTS + slot) else player.slotsChargeFlag.remove(PlayerInventoryWrapper.SLOTS + slot) }
override var filter: Item? override var filter: ItemFilter
get() = player.slotFilters[PlayerInventoryWrapper.SLOTS + slot] get() = player.slotFilters[PlayerInventoryWrapper.SLOTS + slot] ?: ItemFilter.EMPTY
set(value) { if (value == null) player.slotFilters.remove(PlayerInventoryWrapper.SLOTS + slot) else player.slotFilters[PlayerInventoryWrapper.SLOTS + slot] = value } set(value) { if (value.allowAll) player.slotFilters.remove(PlayerInventoryWrapper.SLOTS + slot) else player.slotFilters[PlayerInventoryWrapper.SLOTS + slot] = value }
} }
override fun containerSlot(slot: Int): IPlayerInventorySlot { 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.IContainerSlot
import ru.dbotthepony.mc.otm.container.IEnhancedContainer import ru.dbotthepony.mc.otm.container.IEnhancedContainer
import ru.dbotthepony.mc.otm.container.IFilteredContainerSlot 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.get
import ru.dbotthepony.mc.otm.container.slotted.ContainerSlot import ru.dbotthepony.mc.otm.container.slotted.ContainerSlot
import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer
@ -254,7 +255,7 @@ class MatteryPlayer(val ply: Player) {
val slotFilters = syncher.map( val slotFilters = syncher.map(
backing = ListenableMap(Int2ObjectOpenHashMap()), backing = ListenableMap(Int2ObjectOpenHashMap()),
keyCodec = StreamCodecs.VAR_INT, keyCodec = StreamCodecs.VAR_INT,
valueCodec = StreamCodecs.ITEM_TYPE valueCodec = StreamCodecs.ITEM_FILTER
).delegate ).delegate
private fun slotChargeToDefault() { private fun slotChargeToDefault() {
@ -1299,11 +1300,11 @@ class MatteryPlayer(val ply: Player) {
@Suppress("unused") @Suppress("unused")
companion object { companion object {
private val filtersCodec: Codec<List<Pair<Int, Item>>> = Codec.list( private val filtersCodec: Codec<List<Pair<Int, ItemFilter>>> = Codec.list(
RecordCodecBuilder.create { RecordCodecBuilder.create {
it.group( it.group(
Codec.INT.minRange(0).fieldOf("slot").forGetter { it.first }, 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) ).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.Item
import net.minecraft.world.item.ItemStack import net.minecraft.world.item.ItemStack
import ru.dbotthepony.mc.otm.container.ISlottedContainer 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.get
import ru.dbotthepony.mc.otm.container.set import ru.dbotthepony.mc.otm.container.set
@ -17,9 +18,9 @@ class PlayerInventoryWrapper(val player: MatteryPlayer) : ISlottedContainer<IPla
inventory.setChanged() inventory.setChanged()
} }
override var filter: Item? override var filter: ItemFilter
get() = player.slotFilters[slot] get() = player.slotFilters[slot] ?: ItemFilter.EMPTY
set(value) { if (value == null) player.slotFilters.remove(slot) else player.slotFilters[slot] = value } set(value) { if (value.allowAll) player.slotFilters.remove(slot) else player.slotFilters[slot] = value }
override var shouldCharge: Boolean override var shouldCharge: Boolean
get() = slot in player.slotsChargeFlag get() = slot in player.slotsChargeFlag
set(value) { if (value) player.slotsChargeFlag.add(slot) else player.slotsChargeFlag.remove(slot) } 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 ANDROID_FEATURE by Delegate(MRegistries.ANDROID_FEATURE) { sync(true) }
val STACK_TYPE by Delegate(MRegistries.STACK_TYPE) 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) { internal fun register(bus: IEventBus) {
delegates.forEach { bus.addListener(it::build) } 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.core.Registry
import net.minecraft.resources.ResourceKey import net.minecraft.resources.ResourceKey
import ru.dbotthepony.mc.otm.OverdriveThatMatters 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.core.ResourceLocation
import ru.dbotthepony.mc.otm.data.world.DecimalProvider import ru.dbotthepony.mc.otm.data.world.DecimalProvider
import ru.dbotthepony.mc.otm.matter.AbstractRegistryAction 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_RESEARCH_RESULT = k<AndroidResearchResult.Type<*>>("android_research_result")
val ANDROID_FEATURE = k<AndroidFeatureType<*>>("android_feature") val ANDROID_FEATURE = k<AndroidFeatureType<*>>("android_feature")
val STACK_TYPE = k<StorageStack.Type<*>>("stack_type") 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 com.mojang.serialization.Codec
import net.minecraft.core.UUIDUtil import net.minecraft.core.UUIDUtil
import net.minecraft.core.component.DataComponentType import net.minecraft.core.component.DataComponentType
import net.minecraft.core.component.DataComponents
import net.minecraft.core.registries.BuiltInRegistries import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.CompoundTag
import net.minecraft.network.RegistryFriendlyByteBuf import net.minecraft.network.RegistryFriendlyByteBuf
@ -15,7 +14,7 @@ import net.neoforged.bus.api.IEventBus
import net.neoforged.neoforge.fluids.SimpleFluidContent import net.neoforged.neoforge.fluids.SimpleFluidContent
import ru.dbotthepony.mc.otm.capability.FlowDirection import ru.dbotthepony.mc.otm.capability.FlowDirection
import ru.dbotthepony.mc.otm.capability.matter.PatternState 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.core.math.Decimal
import ru.dbotthepony.mc.otm.data.codec.DecimalCodec import ru.dbotthepony.mc.otm.data.codec.DecimalCodec
import ru.dbotthepony.mc.otm.item.tool.RedstoneInteractorItem 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() 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 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() } val EXPERIENCE: DataComponentType<Long> by registry.register("experience") { DataComponentType.builder<Long>().persistent(Codec.LONG).networkSynchronized(StreamCodecs.LONG).build() }