Update ItemFilter to behave like it did previously, with fixed amount of slots but still being immutable

This commit is contained in:
DBotThePony 2024-08-12 20:18:39 +07:00
parent aa5d36c488
commit 5b4fa2e9ed
Signed by: DBot
GPG Key ID: DCC23B5715498507
11 changed files with 161 additions and 89 deletions

View File

@ -122,13 +122,15 @@ class StorageBusBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matter
})
}
val filter = ItemFilter(MAX_FILTERS) {
component?.scan()
markDirtyFast()
}
var filter = ItemFilter(MAX_FILTERS)
set(value) {
field = value
component?.scan()
markDirtyFast()
}
init {
savetables.stateful(::filter, FILTER_KEY)
savetables.codec(::filter, ItemFilter.CODEC, FILTER_KEY)
}
override fun setLevel(level: Level) {

View File

@ -97,10 +97,11 @@ abstract class AbstractStorageImportExport(
protected val target = CapabilityCache(RelativeSide.FRONT, Capabilities.ItemHandler.BLOCK)
var filter: ItemFilter = ItemFilter.EMPTY
var filter: ItemFilter = ItemFilter(MAX_FILTERS)
set(value) {
if (value != field) {
field = value
markDirtyFast()
itemFilterUpdated()
}
}
@ -115,6 +116,7 @@ abstract class AbstractStorageImportExport(
companion object {
const val FILTER_KEY = "filter"
const val MAX_FILTERS = 6 * 3
}
}
@ -263,7 +265,6 @@ class StorageExporterBlockEntity(blockPos: BlockPos, blockState: BlockState) :
}
lastSlot = 0
markDirtyFast()
}
private var lastSlot = 0

View File

@ -73,7 +73,7 @@ class DriveViewerScreen(menu: DriveViewerMenu, inventory: Inventory, title: Comp
it.dock = Dock.LEFT
CheckBoxLabelInputPanel(this, it, menu.isWhitelist, TranslatableComponent("otm.gui.filter.is_whitelist")).also { it.dockTop = 20f; it.dock = Dock.TOP }
CheckBoxLabelInputPanel(this, it, menu.matchTag, TranslatableComponent("otm.gui.filter.match_tag")).also { it.dockTop = 4f; it.dock = Dock.TOP }
CheckBoxLabelInputPanel(this, it, menu.matchNBT, TranslatableComponent("otm.gui.filter.match_nbt")).also { it.dockTop = 4f; it.dock = Dock.TOP }
CheckBoxLabelInputPanel(this, it, menu.matchComponents, TranslatableComponent("otm.gui.filter.match_nbt")).also { it.dockTop = 4f; it.dock = Dock.TOP }
})
frame.Tab(view, activeIcon = ItemStackIcon(ItemStack(MItems.PORTABLE_CONDENSATION_DRIVE)))

View File

@ -8,10 +8,8 @@ import ru.dbotthepony.mc.otm.client.screen.panels.*
import ru.dbotthepony.mc.otm.client.screen.panels.button.CheckBoxLabelInputPanel
import ru.dbotthepony.mc.otm.client.screen.panels.button.makeDeviceControls
import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.slot.BatterySlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.slot.FilterSlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.util.GridPanel
import ru.dbotthepony.mc.otm.client.screen.widget.WidePowerGaugePanel
import ru.dbotthepony.mc.otm.menu.storage.StorageImporterExporterMenu
@ -26,23 +24,23 @@ class StorageImporterExporterScreen(menu: StorageImporterExporterMenu, inventory
val grid = GridPanel(this, right, columns = 6, rows = 3, height = AbstractSlotPanel.SIZE * 3f)
grid.dock = Dock.TOP
for (slot in menu.filterSlots) {
for (slot in menu.filter.slots) {
FilterSlotPanel(this, grid, slot)
}
CheckBoxLabelInputPanel(this, right, menu.isWhitelist, TranslatableComponent("otm.gui.filter.is_whitelist")).also {
CheckBoxLabelInputPanel(this, right, menu.filter.isWhitelist, TranslatableComponent("otm.gui.filter.is_whitelist")).also {
it.dock = Dock.BOTTOM
it.dockTop = 2f
it.childrenOrder = -1
}
CheckBoxLabelInputPanel(this, right, menu.matchNBT, TranslatableComponent("otm.gui.filter.match_nbt")).also {
CheckBoxLabelInputPanel(this, right, menu.filter.matchComponents, TranslatableComponent("otm.gui.filter.match_nbt")).also {
it.dock = Dock.BOTTOM
it.dockTop = 2f
it.childrenOrder = -2
}
CheckBoxLabelInputPanel(this, right, menu.matchTag, TranslatableComponent("otm.gui.filter.match_tag")).also {
CheckBoxLabelInputPanel(this, right, menu.filter.matchTag, TranslatableComponent("otm.gui.filter.match_tag")).also {
it.dock = Dock.BOTTOM
it.dockTop = 2f
it.childrenOrder = -3

View File

@ -1,36 +1,48 @@
package ru.dbotthepony.mc.otm.container
import com.google.common.collect.ImmutableList
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
import it.unimi.dsi.fastutil.objects.ObjectArrayList
import net.minecraft.tags.TagKey
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
data class ItemFilter(val filter: List<ItemStack>, val isWhitelist: Boolean = false, val matchTag: Boolean = false, val matchComponents: Boolean = false) {
fun add(item: ItemStack): ItemFilter {
if (filter.any { ItemStack.isSameItemSameComponents(it, item) })
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
}
override fun hashCode(): Int {
return filter.contentHashCode()
}
val size: Int
get() = filter.size
fun set(index: Int, value: ItemStack): ItemFilter {
if (ItemStack.isSameItemSameComponents(filter[index], value) || filter.any { ItemStack.isSameItemSameComponents(it, value) })
return this
return copy(
filter = ArrayList(filter).also { it.add(item) }
)
return copy(filter.copyOf().also { it[index] = value })
}
fun remove(item: ItemStack): ItemFilter {
val indexOf = filter.indexOfFirst { ItemStack.isSameItemSameComponents(it, item) }
if (indexOf == -1)
return this
return copy(
filter = ArrayList(filter).also { it.removeAt(indexOf) }
)
operator fun get(index: Int): ItemStack {
return filter[index]
}
fun get(index: Int): ItemStack {
return filter.getOrElse(index) { ItemStack.EMPTY }
}
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)
fun isWhitelist(flag: Boolean): ItemFilter {
if (flag == isWhitelist)
@ -96,12 +108,12 @@ data class ItemFilter(val filter: List<ItemStack>, val isWhitelist: Boolean = fa
}
companion object {
val EMPTY = ItemFilter(ImmutableList.of())
val EMPTY = ItemFilter(0)
val CODEC: Codec<ItemFilter> by lazy {
RecordCodecBuilder.create<ItemFilter> {
it.group(
Codec.list(ItemStack.CODEC, 0, 40).fieldOf("filter").forGetter { it.filter },
Codec.list(ItemStack.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 },

View File

@ -55,8 +55,19 @@ class PortableCondensationDriveItem(capacity: Int) : Item(Properties().stacksTo(
}, this)
}
fun getFilterSettings(item: ItemStack): ItemFilter {
return item.getOrDefault(MDataComponentTypes.ITEM_FILTER, EMPTY_FILTER)
}
fun setFilterSettings(item: ItemStack, filter: ItemFilter) {
item.set(MDataComponentTypes.ITEM_FILTER, filter)
}
@Suppress("unused")
companion object {
const val MAX_FILTERS = 4 * 3
private val EMPTY_FILTER = ItemFilter(MAX_FILTERS)
fun onPickupEvent(event: ItemEntityPickupEvent.Pre) {
if (event.itemEntity.owner != null && event.itemEntity.owner != event.player && event.itemEntity.age < 200 || event.itemEntity.item.isEmpty) {
return

View File

@ -25,7 +25,6 @@ import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.enchantment.EnchantmentEffectComponents
import net.minecraft.world.item.enchantment.EnchantmentHelper
import net.minecraft.world.item.enchantment.Enchantments
import net.minecraft.world.level.block.entity.BlockEntity
import net.neoforged.neoforge.network.PacketDistributor
import net.neoforged.neoforge.network.handling.IPayloadContext
@ -42,7 +41,6 @@ import ru.dbotthepony.mc.otm.compat.cos.cosmeticArmorSlots
import ru.dbotthepony.mc.otm.compat.curios.curiosSlots
import ru.dbotthepony.mc.otm.compat.curios.isCurioSlot
import ru.dbotthepony.mc.otm.container.IMatteryContainer
import ru.dbotthepony.mc.otm.container.ItemFilter
import ru.dbotthepony.mc.otm.container.UpgradeContainer
import ru.dbotthepony.mc.otm.container.computeSortedIndices
import ru.dbotthepony.mc.otm.container.sortWithIndices
@ -241,42 +239,6 @@ abstract class MatteryMenu(
protected var inventorySlotIndexStart = 0
protected var inventorySlotIndexEnd = 0
fun addFilterSlots(slots: ItemFilter): List<Delegate<ItemStack>> {
val result = ArrayList<Delegate<ItemStack>>(slots.size)
for (i in 0 until slots.size) {
result.add(Delegate.Of(
mSynchronizer.computedItem { slots[i] },
itemStackInput { slots[i] = it }
))
}
return result
}
fun 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 addFilterSlots(slots: ItemFilter?, amount: Int): List<Delegate<ItemStack>> {
if (slots != null && amount != slots.size)
throw IllegalStateException("Provided ItemFiler has different amount of slots than expected: ${slots.size} != $amount")
if (slots == null)
return addFilterSlots(amount)
else
return addFilterSlots(slots)
}
open inner class InventorySlot(container: Container, index: Int, addFilter: Boolean = false) : UserFilteredSlot(container, index, 0, 0) {
override fun mayPlace(itemStack: ItemStack): Boolean {
return !isInventorySlotLocked(index) && super.mayPlace(itemStack)

View File

@ -9,14 +9,19 @@ 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.value
import ru.dbotthepony.mc.otm.capability.FlowDirection
import ru.dbotthepony.mc.otm.capability.MatteryCapability
import ru.dbotthepony.mc.otm.capability.energy
import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.container.IMatteryContainer
import ru.dbotthepony.mc.otm.container.ItemFilter
import ru.dbotthepony.mc.otm.core.immutableList
import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback
import ru.dbotthepony.mc.otm.runOnClient
import java.util.ArrayList
import java.util.function.Predicate
import kotlin.reflect.KMutableProperty0
/**
* Make slots for single container
@ -159,3 +164,62 @@ open class DriveSlot(container: Container, index: Int, x: Int = 0, y: Int = 0) :
return super.mayPlace(itemStack) && itemStack.getCapability(MatteryCapability.CONDENSATION_DRIVE) != null
}
}
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): 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(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)
}

View File

@ -3,6 +3,8 @@ package ru.dbotthepony.mc.otm.menu.input
import ru.dbotthepony.kommons.util.Delegate
import ru.dbotthepony.mc.otm.menu.MatteryMenu
import java.util.function.BooleanSupplier
import java.util.function.Consumer
import java.util.function.Supplier
import kotlin.reflect.KMutableProperty0
class BooleanInputWithFeedback(menu: MatteryMenu, allowSpectators: Boolean = false) : AbstractPlayerInputWithFeedback<Boolean>() {
@ -32,4 +34,28 @@ class BooleanInputWithFeedback(menu: MatteryMenu, allowSpectators: Boolean = fal
fun switchValue() {
accept(!value)
}
companion object {
fun <T> dispatch(menu: MatteryMenu, property: Delegate<T>, getter: (T) -> Boolean, setter: (T, Boolean) -> T): BooleanInputWithFeedback {
return BooleanInputWithFeedback(
menu,
Delegate.Of(
{ getter(property.get()) },
{ property.accept(setter(property.get(), it)) }
)
)
}
fun <T> dispatch(menu: MatteryMenu, property: KMutableProperty0<T>, getter: (T) -> Boolean, setter: (T, Boolean) -> T): BooleanInputWithFeedback {
return BooleanInputWithFeedback(
menu,
Delegate.Of(
{ getter(property.get()) },
{ property.set(setter(property.get(), it)) }
)
)
}
}
}

View File

@ -15,7 +15,6 @@ import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage
import ru.dbotthepony.mc.otm.container.ItemFilter
import ru.dbotthepony.mc.otm.core.immutableList
import ru.dbotthepony.mc.otm.core.util.ItemStorageStackSorter
import ru.dbotthepony.mc.otm.core.util.computedItem
import ru.dbotthepony.mc.otm.item.PortableCondensationDriveItem
import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu
import ru.dbotthepony.mc.otm.menu.MatterySlot
@ -81,23 +80,21 @@ class DriveViewerMenu(
return (stack?.item as? PortableCondensationDriveItem)?.getFilterSettings(stack)
}
private fun setFilter(value: ItemFilter) {
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) }
itemStackInput { getFilter()?.set(i, it) }.filter { drivePresent }
)
}
private fun make(mapper: (ItemFilter) -> Delegate<Boolean>): Delegate<Boolean> {
return Delegate.Of(
{ getFilter()?.let(mapper)?.get() ?: false },
{ getFilter()?.let(mapper)?.accept(it) }
)
}
val isWhitelist = BooleanInputWithFeedback(this, make { Delegate.Of(it::isWhitelist) }).also { it.filter { drivePresent } }
val matchTag = BooleanInputWithFeedback(this, make { Delegate.Of(it::matchTag) }).also { it.filter { drivePresent } }
val matchNBT = BooleanInputWithFeedback(this, make { Delegate.Of(it::matchComponents) }).also { 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 } }
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 } }
override fun broadcastChanges() {
super.broadcastChanges()

View File

@ -2,7 +2,9 @@ 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.container.ItemFilter
import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu
import ru.dbotthepony.mc.otm.menu.addFilterControls
import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback
import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput
import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget
@ -11,10 +13,7 @@ import ru.dbotthepony.mc.otm.registry.MMenus
class StorageImporterExporterMenu(
containerId: Int, inventory: Inventory, tile: AbstractStorageImportExport? = null
) : MatteryPoweredMenu(MMenus.STORAGE_IMPORTER_EXPORTER, containerId, inventory, tile) {
val filterSlots = addFilterSlots(tile?.filter, AbstractStorageImportExport.MAX_FILTERS)
val isWhitelist = BooleanInputWithFeedback(this, tile?.let { it.filter::isWhitelist })
val matchNBT = BooleanInputWithFeedback(this, tile?.let { it.filter::matchComponents })
val matchTag = BooleanInputWithFeedback(this, tile?.let { it.filter::matchTag })
val filter = addFilterControls(tile?.let { it::filter }, AbstractStorageImportExport.MAX_FILTERS)
val profiledEnergy = ProfiledLevelGaugeWidget(this, tile?.energy, energyWidget)
val energyConfig = EnergyConfigPlayerInput(this, tile?.energyConfig)