diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerHelpers.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerHelpers.kt index 4a3f69ce2..cb61feae0 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerHelpers.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerHelpers.kt @@ -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() diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/IEnhancedContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/IEnhancedContainer.kt index 34a1ddd75..c7deb64af 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/IEnhancedContainer.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/IEnhancedContainer.kt @@ -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 { } fun nonEmptySlotIterator(): Iterator { - return (0 until containerSize).iterator().map { containerSlot(it) }.filter { it.isNotEmpty } + return slotIterator().filter { it.isNotEmpty } } - private fun slotIterator(allowedSlots: IntSet, predicate: Predicate): IntIterator { + private fun slotIterator(allowedSlots: IntCollection, predicate: Predicate): IntIterator { return object : IntIterator() { private val parent = allowedSlots.intIterator() private var foundNext = false @@ -76,15 +77,15 @@ interface IEnhancedContainer : IContainer, RecipeInput, Iterable { } } - 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 } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ISlottedContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ISlottedContainer.kt index c6cdb88ad..58430a103 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ISlottedContainer.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ISlottedContainer.kt @@ -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 { - return (0 until containerSize).iterator().map { containerSlot(it) } - } - - override fun nonEmptySlotIterator(): Iterator { - 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 diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/slotted/ContainerSlot.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/slotted/ContainerSlot.kt index 990bb32fc..7ab61cebd 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/slotted/ContainerSlot.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/slotted/ContainerSlot.kt @@ -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 = AutomationPlaceItem { _, _ -> true }, + private val canAutomationTakeItem: AutomationTakeItem = AutomationTakeItem { _, _ -> true }, + private val modifyAutomationPlaceCount: AutomationModifyPlaceCount = AutomationModifyPlaceCount { _, item -> item.count }, + private val modifyAutomationExtractionCount: AutomationModifyExtractionCount = AutomationModifyExtractionCount { _, desired -> desired }, + ) : SlottedContainer.SlotProvider { + 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) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/slotted/FilteredContainerSlot.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/slotted/FilteredContainerSlot.kt new file mode 100644 index 000000000..4a067fd75 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/slotted/FilteredContainerSlot.kt @@ -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 = AutomationPlaceItem { _, _ -> true }, + private val canAutomationTakeItem: AutomationTakeItem = AutomationTakeItem { _, _ -> true }, + private val modifyAutomationPlaceCount: AutomationModifyPlaceCount = AutomationModifyPlaceCount { _, item -> item.count }, + private val modifyAutomationExtractionCount: AutomationModifyExtractionCount = AutomationModifyExtractionCount { _, desired -> desired }, + ) : SlottedContainer.SlotProvider { + 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) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/slotted/SimpleCallbacks.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/slotted/SimpleCallbacks.kt new file mode 100644 index 000000000..664d268f0 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/slotted/SimpleCallbacks.kt @@ -0,0 +1,19 @@ +package ru.dbotthepony.mc.otm.container.slotted + +import net.minecraft.world.item.ItemStack + +fun interface AutomationPlaceItem { + fun canAutomationPlaceItem(self: S, itemStack: ItemStack): Boolean +} + +fun interface AutomationTakeItem { + fun canAutomationTakeItem(self: S, desired: Int): Boolean +} + +fun interface AutomationModifyPlaceCount { + fun modifyAutomationPlaceCount(self: S, itemStack: ItemStack): Int +} + +fun interface AutomationModifyExtractionCount { + fun modifyAutomationExtractionCount(self: S, desired: Int): Int +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/slotted/SlottedContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/slotted/SlottedContainer.kt index 45c7d60b9..c342b464c 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/slotted/SlottedContainer.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/slotted/SlottedContainer.kt @@ -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, + slots: Collection>, private val stillValid: Predicate, private val globalChangeListeners: Array ) : IAutomatedContainer, INBTSerializable { + interface ISlotGroup : List { + /** + * @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 : ISlotGroup, AbstractList() { + 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(val clazz: KClass) { + override fun toString(): String { + return "SlottedContainer.SingleTag[${System.identityHashCode(this).toString(16)}@${clazz.qualifiedName}]" + } + } + + class MultiTag(val clazz: KClass) { + override fun toString(): String { + return "SlottedContainer.MultiTag[${System.identityHashCode(this).toString(16)}@${clazz.qualifiedName}]" + } + } + + private val sets = HashMap, SlotGroup<*>>() + private val singular = HashMap, ContainerSlot>() private val slots: Array 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() }.slots.add(index) }) + slot + } + } + + operator fun get(tag: MultiTag): ISlotGroup { + return sets[tag] as ISlotGroup? ?: throw NoSuchElementException("Container does not contain $tag") + } + + operator fun get(tag: SingleTag): 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 { + fun create(container: SlottedContainer, index: Int): T } + data class MarkedSlotProvider(val mark: Either, MultiTag>?, val provider: SlotProvider) + class Builder { - private val slots = ArrayList() + private val slots = ArrayList>() private var stillValid = Predicate { true } private val globalChangeListeners = ArrayList() + private val seenSingleTags = ObjectOpenHashSet>() - 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 add(tag: SingleTag, slot: SlotProvider): Builder { + require(seenSingleTags.add(tag)) { "Duplicate slot tag: $tag" } + slots.add(MarkedSlotProvider(Either.left(tag), slot)) + return this + } + + fun add(tag: MultiTag, slot: SlotProvider): 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 add(amount: Int, tag: MultiTag, provider: SlotProvider): 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 tag(): SingleTag { + return SingleTag(T::class) + } + + inline fun tagList(): MultiTag { + 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() + } } }