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
val empty = IntArrayList()

View File

@ -1,5 +1,6 @@
package ru.dbotthepony.mc.otm.container
import it.unimi.dsi.fastutil.ints.IntCollection
import it.unimi.dsi.fastutil.ints.IntSet
import net.minecraft.world.Container
import net.minecraft.world.entity.player.Player
@ -32,10 +33,10 @@ interface IEnhancedContainer : IContainer, RecipeInput, Iterable<ItemStack> {
}
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() {
private val parent = allowedSlots.intIterator()
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 }
}
fun nonEmptySlotIterator(allowedSlots: IntSet = slotRange): IntIterator {
fun nonEmptySlotIterator(allowedSlots: IntCollection = slotRange): IntIterator {
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 }
}

View File

@ -1,5 +1,6 @@
package ru.dbotthepony.mc.otm.container
import it.unimi.dsi.fastutil.ints.IntCollection
import it.unimi.dsi.fastutil.ints.IntSet
import net.minecraft.world.Container
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
/**
* Container which revolve around embedding slot objects rather than providing direct item access
* Skeletal implementation for containers which revolve around [IContainerSlot]
*/
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) {
containerSlot(slot).setChanged()
}
@ -44,7 +37,7 @@ interface ISlottedContainer : IEnhancedContainer {
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())
return stack
@ -129,7 +122,7 @@ interface ISlottedContainer : IEnhancedContainer {
val hasFilterableSlots: Boolean
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())
return stack
@ -148,7 +141,7 @@ interface ISlottedContainer : IEnhancedContainer {
*
* @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)
return false
@ -157,7 +150,7 @@ interface ISlottedContainer : IEnhancedContainer {
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)
return false

View File

@ -109,11 +109,15 @@ open class ContainerSlot(
notifyChanged(ItemStack.EMPTY)
}
open class Simple(
protected val listener: (new: ItemStack, old: ItemStack) -> Unit,
protected val maxStackSize: Int = Item.DEFAULT_MAX_STACK_SIZE,
) : SlottedContainer.SlotProvider {
protected open inner class Instance(container: SlottedContainer, slot: Int) : ContainerSlot(container, slot) {
class Simple(
private val listener: (new: ItemStack, old: ItemStack) -> Unit = { _, _ -> },
private val maxStackSize: Int = Item.DEFAULT_MAX_STACK_SIZE,
private val canAutomationPlaceItem: AutomationPlaceItem<ContainerSlot> = AutomationPlaceItem { _, _ -> true },
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
get() = this@Simple.maxStackSize
@ -121,45 +125,21 @@ open class ContainerSlot(
super.notifyChanged(old)
listener(item, old)
}
}
override fun create(container: SlottedContainer, index: Int): ContainerSlot {
return Instance(container, index)
}
}
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 canAutomationPlaceItem(itemStack: ItemStack): Boolean {
return super.canAutomationPlaceItem(itemStack) && canAutomationPlaceItem.canAutomationPlaceItem(this, itemStack)
}
override fun serializeNBT(provider: HolderLookup.Provider): CompoundTag {
return super.serializeNBT(provider).also {
if (filter != null)
it["filter"] = filter!!.registryName!!.toString()
}
override fun canAutomationTakeItem(desired: Int): Boolean {
return super.canAutomationTakeItem(desired) && canAutomationTakeItem.canAutomationTakeItem(this, desired)
}
override fun deserializeNBT(provider: HolderLookup.Provider, nbt: CompoundTag) {
super.deserializeNBT(provider, nbt)
override fun modifyAutomationPlaceCount(itemStack: ItemStack): Int {
return modifyAutomationPlaceCount.modifyAutomationPlaceCount(this, itemStack)
}
if ("filter" in nbt) {
filter = BuiltInRegistries.ITEM.get(ResourceLocation.parse(nbt.getString("filter")))
}
override fun modifyAutomationExtractionCount(desired: Int): Int {
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.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.registries.BuiltInRegistries
import net.minecraft.nbt.CompoundTag
@ -14,24 +19,106 @@ import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.neoforged.neoforge.common.util.INBTSerializable
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.IAutomatedContainerSlot
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.nbt.set
import ru.dbotthepony.mc.otm.data.codec.minRange
import java.util.function.Predicate
import kotlin.reflect.KClass
class SlottedContainer(
slots: Collection<SlotProvider>,
slots: Collection<MarkedSlotProvider<*>>,
private val stillValid: Predicate<Player>,
private val globalChangeListeners: Array<Runnable>
) : 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>
init {
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 }
@ -218,23 +305,44 @@ class SlottedContainer(
notifyChanged()
}
fun interface SlotProvider {
fun create(container: SlottedContainer, index: Int): ContainerSlot
fun interface SlotProvider<T : 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 {
private val slots = ArrayList<SlotProvider>()
private val slots = ArrayList<MarkedSlotProvider<*>>()
private var stillValid = Predicate<Player> { true }
private val globalChangeListeners = ArrayList<Runnable>()
private val seenSingleTags = ObjectOpenHashSet<SingleTag<*>>()
fun add(slot: SlotProvider): Builder {
slots.add(slot)
fun add(slot: SlotProvider<*>): Builder {
slots.add(MarkedSlotProvider(null, slot))
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)
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
}
@ -262,8 +370,37 @@ class SlottedContainer(
}
}
companion object {
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()
}
}
}