Some helper functions, split "Filtered" into own subclass with own "Simple" factory

This commit is contained in:
DBotThePony 2025-02-28 15:23:08 +07:00
parent ab1446b682
commit 9f6b9ad85b
Signed by: DBot
GPG Key ID: DCC23B5715498507
7 changed files with 280 additions and 67 deletions

View File

@ -56,7 +56,7 @@ fun Container.vanishCursedItems() {
} }
} }
fun Container.balance(slots: IntSet, checkForEmpty: Boolean = true) { fun Container.balance(slots: IntCollection, checkForEmpty: Boolean = true) {
if (slots.isEmpty() || checkForEmpty && !slots.any { getItem(it).isNotEmpty }) return if (slots.isEmpty() || checkForEmpty && !slots.any { getItem(it).isNotEmpty }) return
val empty = IntArrayList() val empty = IntArrayList()

View File

@ -1,5 +1,6 @@
package ru.dbotthepony.mc.otm.container package ru.dbotthepony.mc.otm.container
import it.unimi.dsi.fastutil.ints.IntCollection
import it.unimi.dsi.fastutil.ints.IntSet import it.unimi.dsi.fastutil.ints.IntSet
import net.minecraft.world.Container import net.minecraft.world.Container
import net.minecraft.world.entity.player.Player import net.minecraft.world.entity.player.Player
@ -32,10 +33,10 @@ interface IEnhancedContainer : IContainer, RecipeInput, Iterable<ItemStack> {
} }
fun nonEmptySlotIterator(): Iterator<IContainerSlot> { fun nonEmptySlotIterator(): Iterator<IContainerSlot> {
return (0 until containerSize).iterator().map { containerSlot(it) }.filter { it.isNotEmpty } return slotIterator().filter { it.isNotEmpty }
} }
private fun slotIterator(allowedSlots: IntSet, predicate: Predicate<ItemStack>): IntIterator { private fun slotIterator(allowedSlots: IntCollection, predicate: Predicate<ItemStack>): IntIterator {
return object : IntIterator() { return object : IntIterator() {
private val parent = allowedSlots.intIterator() private val parent = allowedSlots.intIterator()
private var foundNext = false private var foundNext = false
@ -76,15 +77,15 @@ interface IEnhancedContainer : IContainer, RecipeInput, Iterable<ItemStack> {
} }
} }
fun emptySlotIterator(allowedSlots: IntSet = slotRange): IntIterator { fun emptySlotIterator(allowedSlots: IntCollection = slotRange): IntIterator {
return slotIterator(allowedSlots) { it.isEmpty } return slotIterator(allowedSlots) { it.isEmpty }
} }
fun nonEmptySlotIterator(allowedSlots: IntSet = slotRange): IntIterator { fun nonEmptySlotIterator(allowedSlots: IntCollection = slotRange): IntIterator {
return slotIterator(allowedSlots) { it.isNotEmpty } return slotIterator(allowedSlots) { it.isNotEmpty }
} }
fun slotWithItemIterator(item: Item, allowedSlots: IntSet = slotRange): IntIterator { fun slotWithItemIterator(item: Item, allowedSlots: IntCollection = slotRange): IntIterator {
return slotIterator(allowedSlots) { it.isNotEmpty && it.item === item } return slotIterator(allowedSlots) { it.isNotEmpty && it.item === item }
} }

View File

@ -1,5 +1,6 @@
package ru.dbotthepony.mc.otm.container package ru.dbotthepony.mc.otm.container
import it.unimi.dsi.fastutil.ints.IntCollection
import it.unimi.dsi.fastutil.ints.IntSet import it.unimi.dsi.fastutil.ints.IntSet
import net.minecraft.world.Container import net.minecraft.world.Container
import net.minecraft.world.item.ItemStack import net.minecraft.world.item.ItemStack
@ -9,17 +10,9 @@ import ru.dbotthepony.mc.otm.core.collect.filter
import ru.dbotthepony.mc.otm.core.collect.map import ru.dbotthepony.mc.otm.core.collect.map
/** /**
* Container which revolve around embedding slot objects rather than providing direct item access * Skeletal implementation for containers which revolve around [IContainerSlot]
*/ */
interface ISlottedContainer : IEnhancedContainer { interface ISlottedContainer : IEnhancedContainer {
override fun slotIterator(): Iterator<IContainerSlot> {
return (0 until containerSize).iterator().map { containerSlot(it) }
}
override fun nonEmptySlotIterator(): Iterator<IContainerSlot> {
return (0 until containerSize).iterator().map { containerSlot(it) }.filter { it.isNotEmpty }
}
override fun setChanged(slot: Int) { override fun setChanged(slot: Int) {
containerSlot(slot).setChanged() containerSlot(slot).setChanged()
} }
@ -44,7 +37,7 @@ interface ISlottedContainer : IEnhancedContainer {
containerSlot(slot).item = itemStack containerSlot(slot).item = itemStack
} }
private fun addItem(stack: ItemStack, simulate: Boolean, filterPass: Boolean, slots: IntSet, onlyIntoExisting: Boolean, popTime: Int?, ignoreFilters: Boolean): ItemStack { private fun addItem(stack: ItemStack, simulate: Boolean, filterPass: Boolean, slots: IntCollection, onlyIntoExisting: Boolean, popTime: Int?, ignoreFilters: Boolean): ItemStack {
if (stack.isEmpty || slots.isEmpty()) if (stack.isEmpty || slots.isEmpty())
return stack return stack
@ -129,7 +122,7 @@ interface ISlottedContainer : IEnhancedContainer {
val hasFilterableSlots: Boolean val hasFilterableSlots: Boolean
get() = slotIterator().any { it is IFilteredContainerSlot } get() = slotIterator().any { it is IFilteredContainerSlot }
fun addItem(stack: ItemStack, simulate: Boolean, slots: IntSet = slotRange, onlyIntoExisting: Boolean = false, popTime: Int? = null, ignoreFilters: Boolean): ItemStack { fun addItem(stack: ItemStack, simulate: Boolean, slots: IntCollection = slotRange, onlyIntoExisting: Boolean = false, popTime: Int? = null, ignoreFilters: Boolean): ItemStack {
if (stack.isEmpty || slots.isEmpty()) if (stack.isEmpty || slots.isEmpty())
return stack return stack
@ -148,7 +141,7 @@ interface ISlottedContainer : IEnhancedContainer {
* *
* @return Whenever [stack] was modified * @return Whenever [stack] was modified
*/ */
fun consumeItem(stack: ItemStack, simulate: Boolean, slots: IntSet = slotRange, onlyIntoExisting: Boolean = false, popTime: Int? = null, ignoreFilters: Boolean): Boolean { fun consumeItem(stack: ItemStack, simulate: Boolean, slots: IntCollection = slotRange, onlyIntoExisting: Boolean = false, popTime: Int? = null, ignoreFilters: Boolean): Boolean {
if (stack.isEmpty) if (stack.isEmpty)
return false return false
@ -157,7 +150,7 @@ interface ISlottedContainer : IEnhancedContainer {
return result.count != stack.count return result.count != stack.count
} }
fun fullyAddItem(stack: ItemStack, slots: IntSet = slotRange, onlyIntoExisting: Boolean = false, popTime: Int? = null, ignoreFilters: Boolean): Boolean { fun fullyAddItem(stack: ItemStack, slots: IntCollection = slotRange, onlyIntoExisting: Boolean = false, popTime: Int? = null, ignoreFilters: Boolean): Boolean {
if (!addItem(stack, true, slots, popTime = popTime, onlyIntoExisting = onlyIntoExisting, ignoreFilters = ignoreFilters).isEmpty) if (!addItem(stack, true, slots, popTime = popTime, onlyIntoExisting = onlyIntoExisting, ignoreFilters = ignoreFilters).isEmpty)
return false return false

View File

@ -109,11 +109,15 @@ open class ContainerSlot(
notifyChanged(ItemStack.EMPTY) notifyChanged(ItemStack.EMPTY)
} }
open class Simple( class Simple(
protected val listener: (new: ItemStack, old: ItemStack) -> Unit, private val listener: (new: ItemStack, old: ItemStack) -> Unit = { _, _ -> },
protected val maxStackSize: Int = Item.DEFAULT_MAX_STACK_SIZE, private val maxStackSize: Int = Item.DEFAULT_MAX_STACK_SIZE,
) : SlottedContainer.SlotProvider { private val canAutomationPlaceItem: AutomationPlaceItem<ContainerSlot> = AutomationPlaceItem { _, _ -> true },
protected open inner class Instance(container: SlottedContainer, slot: Int) : ContainerSlot(container, slot) { private val canAutomationTakeItem: AutomationTakeItem<ContainerSlot> = AutomationTakeItem { _, _ -> true },
private val modifyAutomationPlaceCount: AutomationModifyPlaceCount<ContainerSlot> = AutomationModifyPlaceCount { _, item -> item.count },
private val modifyAutomationExtractionCount: AutomationModifyExtractionCount<ContainerSlot> = AutomationModifyExtractionCount { _, desired -> desired },
) : SlottedContainer.SlotProvider<ContainerSlot> {
private open inner class Instance(container: SlottedContainer, slot: Int) : ContainerSlot(container, slot) {
override val maxStackSize: Int override val maxStackSize: Int
get() = this@Simple.maxStackSize get() = this@Simple.maxStackSize
@ -121,45 +125,21 @@ open class ContainerSlot(
super.notifyChanged(old) super.notifyChanged(old)
listener(item, old) listener(item, old)
} }
}
override fun create(container: SlottedContainer, index: Int): ContainerSlot { override fun canAutomationPlaceItem(itemStack: ItemStack): Boolean {
return Instance(container, index) return super.canAutomationPlaceItem(itemStack) && canAutomationPlaceItem.canAutomationPlaceItem(this, itemStack)
}
}
open class Filtered(
listener: (new: ItemStack, old: ItemStack) -> Unit,
maxStackSize: Int = Item.DEFAULT_MAX_STACK_SIZE,
) : Simple(listener, maxStackSize) {
protected open inner class Instance(container: SlottedContainer, slot: Int) : Simple.Instance(container, slot),
IFilteredAutomatedContainerSlot {
override var filter: Item? = null
set(value) {
if (field !== value) {
field = value
container.notifyChanged()
}
}
override fun clear() {
super.clear()
filter = null
} }
override fun serializeNBT(provider: HolderLookup.Provider): CompoundTag { override fun canAutomationTakeItem(desired: Int): Boolean {
return super.serializeNBT(provider).also { return super.canAutomationTakeItem(desired) && canAutomationTakeItem.canAutomationTakeItem(this, desired)
if (filter != null)
it["filter"] = filter!!.registryName!!.toString()
}
} }
override fun deserializeNBT(provider: HolderLookup.Provider, nbt: CompoundTag) { override fun modifyAutomationPlaceCount(itemStack: ItemStack): Int {
super.deserializeNBT(provider, nbt) return modifyAutomationPlaceCount.modifyAutomationPlaceCount(this, itemStack)
}
if ("filter" in nbt) { override fun modifyAutomationExtractionCount(desired: Int): Int {
filter = BuiltInRegistries.ITEM.get(ResourceLocation.parse(nbt.getString("filter"))) return modifyAutomationExtractionCount.modifyAutomationExtractionCount(this, desired)
}
} }
} }

View File

@ -0,0 +1,83 @@
package ru.dbotthepony.mc.otm.container.slotted
import net.minecraft.core.HolderLookup
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.nbt.CompoundTag
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import ru.dbotthepony.mc.otm.container.IFilteredAutomatedContainerSlot
import ru.dbotthepony.mc.otm.core.nbt.set
import ru.dbotthepony.mc.otm.core.registryName
open class FilteredContainerSlot(
container: SlottedContainer,
slot: Int
) : ContainerSlot(container, slot), IFilteredAutomatedContainerSlot {
override var filter: Item? = null
set(value) {
if (field !== value) {
field = value
container.notifyChanged()
}
}
override fun clear() {
super.clear()
filter = null
}
override fun serializeNBT(provider: HolderLookup.Provider): CompoundTag {
return super.serializeNBT(provider).also {
if (filter != null)
it["filter"] = filter!!.registryName!!.toString()
}
}
override fun deserializeNBT(provider: HolderLookup.Provider, nbt: CompoundTag) {
super.deserializeNBT(provider, nbt)
if ("filter" in nbt) {
filter = BuiltInRegistries.ITEM.get(ResourceLocation.parse(nbt.getString("filter")))
}
}
class Simple(
private val listener: (new: ItemStack, old: ItemStack) -> Unit = { _, _ -> },
private val maxStackSize: Int = Item.DEFAULT_MAX_STACK_SIZE,
private val canAutomationPlaceItem: AutomationPlaceItem<FilteredContainerSlot> = AutomationPlaceItem { _, _ -> true },
private val canAutomationTakeItem: AutomationTakeItem<FilteredContainerSlot> = AutomationTakeItem { _, _ -> true },
private val modifyAutomationPlaceCount: AutomationModifyPlaceCount<FilteredContainerSlot> = AutomationModifyPlaceCount { _, item -> item.count },
private val modifyAutomationExtractionCount: AutomationModifyExtractionCount<FilteredContainerSlot> = AutomationModifyExtractionCount { _, desired -> desired },
) : SlottedContainer.SlotProvider<FilteredContainerSlot> {
private open inner class Instance(container: SlottedContainer, slot: Int) : FilteredContainerSlot(container, slot) {
override val maxStackSize: Int
get() = this@Simple.maxStackSize
override fun notifyChanged(old: ItemStack) {
super.notifyChanged(old)
listener(item, old)
}
override fun canAutomationPlaceItem(itemStack: ItemStack): Boolean {
return super.canAutomationPlaceItem(itemStack) && canAutomationPlaceItem.canAutomationPlaceItem(this, itemStack)
}
override fun canAutomationTakeItem(desired: Int): Boolean {
return super.canAutomationTakeItem(desired) && canAutomationTakeItem.canAutomationTakeItem(this, desired)
}
override fun modifyAutomationPlaceCount(itemStack: ItemStack): Int {
return modifyAutomationPlaceCount.modifyAutomationPlaceCount(this, itemStack)
}
override fun modifyAutomationExtractionCount(desired: Int): Int {
return modifyAutomationExtractionCount.modifyAutomationExtractionCount(this, desired)
}
}
override fun create(container: SlottedContainer, index: Int): FilteredContainerSlot {
return Instance(container, index)
}
}
}

View File

@ -0,0 +1,19 @@
package ru.dbotthepony.mc.otm.container.slotted
import net.minecraft.world.item.ItemStack
fun interface AutomationPlaceItem<S : ContainerSlot> {
fun canAutomationPlaceItem(self: S, itemStack: ItemStack): Boolean
}
fun interface AutomationTakeItem<S : ContainerSlot> {
fun canAutomationTakeItem(self: S, desired: Int): Boolean
}
fun interface AutomationModifyPlaceCount<S : ContainerSlot> {
fun modifyAutomationPlaceCount(self: S, itemStack: ItemStack): Int
}
fun interface AutomationModifyExtractionCount<S : ContainerSlot> {
fun modifyAutomationExtractionCount(self: S, desired: Int): Int
}

View File

@ -2,6 +2,11 @@ package ru.dbotthepony.mc.otm.container.slotted
import com.mojang.serialization.Codec import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder import com.mojang.serialization.codecs.RecordCodecBuilder
import it.unimi.dsi.fastutil.ints.IntArrayList
import it.unimi.dsi.fastutil.ints.IntCollection
import it.unimi.dsi.fastutil.ints.IntList
import it.unimi.dsi.fastutil.ints.IntSet
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
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
@ -14,24 +19,106 @@ import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack import net.minecraft.world.item.ItemStack
import net.neoforged.neoforge.common.util.INBTSerializable import net.neoforged.neoforge.common.util.INBTSerializable
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kommons.util.Either
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.balance
import ru.dbotthepony.mc.otm.container.slotRange
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
import ru.dbotthepony.mc.otm.data.codec.minRange import ru.dbotthepony.mc.otm.data.codec.minRange
import java.util.function.Predicate import java.util.function.Predicate
import kotlin.reflect.KClass
class SlottedContainer( class SlottedContainer(
slots: Collection<SlotProvider>, slots: Collection<MarkedSlotProvider<*>>,
private val stillValid: Predicate<Player>, private val stillValid: Predicate<Player>,
private val globalChangeListeners: Array<Runnable> private val globalChangeListeners: Array<Runnable>
) : IAutomatedContainer, INBTSerializable<Tag> { ) : IAutomatedContainer, INBTSerializable<Tag> {
interface ISlotGroup<T : ContainerSlot> : List<T> {
/**
* @see IAutomatedContainer.addItem
*/
fun addItem(stack: ItemStack, simulate: Boolean, onlyIntoExisting: Boolean = false, popTime: Int? = null, ignoreFilters: Boolean = false): ItemStack
/**
* @see IAutomatedContainer.consumeItem
*/
fun consumeItem(stack: ItemStack, simulate: Boolean, onlyIntoExisting: Boolean = false, popTime: Int? = null, ignoreFilters: Boolean = false): Boolean
/**
* @see IAutomatedContainer.fullyAddItem
*/
fun fullyAddItem(stack: ItemStack, onlyIntoExisting: Boolean = false, popTime: Int? = null, ignoreFilters: Boolean = false): Boolean
fun balance()
}
private inner class SlotGroup<T : ContainerSlot> : ISlotGroup<T>, AbstractList<T>() {
val slots = IntArrayList()
override val size: Int
get() = slots.size
override fun get(index: Int): T {
return this@SlottedContainer.slots[slots.getInt(index)] as T
}
override fun addItem(stack: ItemStack, simulate: Boolean, onlyIntoExisting: Boolean, popTime: Int?, ignoreFilters: Boolean): ItemStack {
return this@SlottedContainer.addItem(stack, simulate, slots, onlyIntoExisting, popTime, ignoreFilters)
}
override fun consumeItem(stack: ItemStack, simulate: Boolean, onlyIntoExisting: Boolean, popTime: Int?, ignoreFilters: Boolean): Boolean {
return this@SlottedContainer.consumeItem(stack, simulate, slots, onlyIntoExisting, popTime, ignoreFilters)
}
override fun fullyAddItem(
stack: ItemStack,
onlyIntoExisting: Boolean,
popTime: Int?,
ignoreFilters: Boolean
): Boolean {
return this@SlottedContainer.fullyAddItem(stack, slots, onlyIntoExisting, popTime, ignoreFilters)
}
override fun balance() {
return this@SlottedContainer.balance(slots)
}
}
class SingleTag<T : ContainerSlot>(val clazz: KClass<T>) {
override fun toString(): String {
return "SlottedContainer.SingleTag[${System.identityHashCode(this).toString(16)}@${clazz.qualifiedName}]"
}
}
class MultiTag<T : ContainerSlot>(val clazz: KClass<T>) {
override fun toString(): String {
return "SlottedContainer.MultiTag[${System.identityHashCode(this).toString(16)}@${clazz.qualifiedName}]"
}
}
private val sets = HashMap<MultiTag<*>, SlotGroup<*>>()
private val singular = HashMap<SingleTag<*>, ContainerSlot>()
private val slots: Array<ContainerSlot> private val slots: Array<ContainerSlot>
init { init {
val itr = slots.iterator() val itr = slots.iterator()
this.slots = Array(slots.size) { itr.next().create(this, it) } this.slots = Array(slots.size) { index ->
val (mark, provider) = itr.next()
val slot = provider.create(this, index)
mark?.map({ require(singular.put(it, slot) == null) { "Duplicate Slot tag: $it" } }, { sets.computeIfAbsent(it) { SlotGroup<ContainerSlot>() }.slots.add(index) })
slot
}
}
operator fun <T : ContainerSlot> get(tag: MultiTag<T>): ISlotGroup<T> {
return sets[tag] as ISlotGroup<T>? ?: throw NoSuchElementException("Container does not contain $tag")
}
operator fun <T : ContainerSlot> get(tag: SingleTag<T>): T {
return singular[tag] as T? ?: throw NoSuchElementException("Container does not contain $tag")
} }
override val hasFilterableSlots: Boolean = this.slots.any { it is IFilteredContainerSlot } override val hasFilterableSlots: Boolean = this.slots.any { it is IFilteredContainerSlot }
@ -218,23 +305,44 @@ class SlottedContainer(
notifyChanged() notifyChanged()
} }
fun interface SlotProvider { fun interface SlotProvider<T : ContainerSlot> {
fun create(container: SlottedContainer, index: Int): ContainerSlot fun create(container: SlottedContainer, index: Int): T
} }
data class MarkedSlotProvider<T : ContainerSlot>(val mark: Either<SingleTag<T>, MultiTag<T>>?, val provider: SlotProvider<T>)
class Builder { class Builder {
private val slots = ArrayList<SlotProvider>() private val slots = ArrayList<MarkedSlotProvider<*>>()
private var stillValid = Predicate<Player> { true } private var stillValid = Predicate<Player> { true }
private val globalChangeListeners = ArrayList<Runnable>() private val globalChangeListeners = ArrayList<Runnable>()
private val seenSingleTags = ObjectOpenHashSet<SingleTag<*>>()
fun add(slot: SlotProvider): Builder { fun add(slot: SlotProvider<*>): Builder {
slots.add(slot) slots.add(MarkedSlotProvider(null, slot))
return this return this
} }
fun add(amount: Int, provider: SlotProvider): Builder { fun <T : ContainerSlot> add(tag: SingleTag<T>, slot: SlotProvider<T>): Builder {
require(seenSingleTags.add(tag)) { "Duplicate slot tag: $tag" }
slots.add(MarkedSlotProvider(Either.left(tag), slot))
return this
}
fun <T : ContainerSlot> add(tag: MultiTag<T>, slot: SlotProvider<T>): Builder {
slots.add(MarkedSlotProvider(Either.right(tag), slot))
return this
}
fun add(amount: Int, provider: SlotProvider<*>): Builder {
for (i in 0 until amount) for (i in 0 until amount)
slots.add(provider) slots.add(MarkedSlotProvider(null, provider))
return this
}
fun <T : ContainerSlot> add(amount: Int, tag: MultiTag<T>, provider: SlotProvider<T>): Builder {
for (i in 0 until amount)
slots.add(MarkedSlotProvider(Either.right(tag), provider))
return this return this
} }
@ -262,8 +370,37 @@ class SlottedContainer(
} }
} }
companion object { companion object {
private val LOGGER = LogManager.getLogger() private val LOGGER = LogManager.getLogger()
inline fun <reified T : ContainerSlot> tag(): SingleTag<T> {
return SingleTag(T::class)
}
inline fun <reified T : ContainerSlot> tagList(): MultiTag<T> {
return MultiTag(T::class)
}
fun simple(size: Int): SlottedContainer {
return Builder().add(size, ::ContainerSlot).build()
}
fun simple(size: Int, listener: Runnable): SlottedContainer {
return Builder()
.add(size, ::ContainerSlot)
.onChanged(listener)
.build()
}
fun filtered(size: Int): SlottedContainer {
return Builder().add(size, ::FilteredContainerSlot).build()
}
fun filtered(size: Int, listener: Runnable): SlottedContainer {
return Builder()
.add(size, ::FilteredContainerSlot)
.onChanged(listener)
.build()
}
} }
} }