From ca6ff30414f5006f618b3f5007d0504faba0749e Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Thu, 27 Feb 2025 19:19:33 +0700 Subject: [PATCH] Initial code for new containers --- .../mc/otm/container/ContainerHelpers.kt | 107 +----- .../mc/otm/container/ContainerSlot.kt | 171 +++++++++ .../mc/otm/container/IContainerSlot.kt | 79 ++-- .../mc/otm/container/IEnhancedContainer.kt | 338 ++++++++++++++++++ .../otm/container/IFilteredContainerSlot.kt | 29 ++ .../IFilteredSlottedContainerSlot.kt | 9 + .../mc/otm/container/IMatteryContainer.kt | 41 +-- .../mc/otm/container/ISlottedContainer.kt | 220 ++++++++++++ .../mc/otm/container/ISlottedContainerSlot.kt | 95 +++++ .../mc/otm/container/MatteryContainer.kt | 36 +- .../mc/otm/container/SlottedContainer.kt | 177 +++++++++ .../otm/container/SlottedContainerBuilder.kt | 48 +++ .../mc/otm/container/util/Iterators.kt | 49 +-- .../mc/otm/core/collect/IntRange2Set.kt | 206 +++++++++++ .../mc/otm/core/collect/Iterables.kt | 30 ++ 15 files changed, 1430 insertions(+), 205 deletions(-) create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerSlot.kt create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/container/IEnhancedContainer.kt create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/container/IFilteredContainerSlot.kt create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/container/IFilteredSlottedContainerSlot.kt create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/container/ISlottedContainer.kt create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/container/ISlottedContainerSlot.kt create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/container/SlottedContainer.kt create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/container/SlottedContainerBuilder.kt create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/IntRange2Set.kt 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 42f781e8f..84b25bfe9 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerHelpers.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerHelpers.kt @@ -9,6 +9,7 @@ import it.unimi.dsi.fastutil.ints.IntIterator import it.unimi.dsi.fastutil.ints.IntList import it.unimi.dsi.fastutil.ints.IntOpenHashSet import it.unimi.dsi.fastutil.ints.IntSet +import it.unimi.dsi.fastutil.ints.IntSortedSet import it.unimi.dsi.fastutil.objects.Object2ObjectFunction import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap import it.unimi.dsi.fastutil.objects.ObjectArrayList @@ -22,6 +23,7 @@ import ru.dbotthepony.mc.otm.container.util.ItemStackHashStrategy import ru.dbotthepony.mc.otm.container.util.containerSlot import ru.dbotthepony.mc.otm.container.util.slotIterator import ru.dbotthepony.mc.otm.core.addAll +import ru.dbotthepony.mc.otm.core.collect.IntRange2Set import ru.dbotthepony.mc.otm.core.collect.filter import ru.dbotthepony.mc.otm.core.collect.toList import ru.dbotthepony.mc.otm.core.isNotEmpty @@ -38,89 +40,12 @@ inline operator fun Container.get(index: Int): ItemStack = getItem(index) @Suppress("nothing_to_inline") inline operator fun IFluidHandler.get(index: Int) = getFluidInTank(index) -val Container.slotRange: IntIterable get() { - return IntIterable { - val i = (0 until containerSize).iterator() - - object : IntIterator { - override fun hasNext(): Boolean { - return i.hasNext() - } - - override fun remove() { - throw UnsupportedOperationException() - } - - override fun nextInt(): Int { - return i.nextInt() - } - } - } +val Container.slotRange: IntRange2Set get() { + return IntRange2Set.openEnded(0, containerSize) } -fun Container.addItem(stack: ItemStack, simulate: Boolean, slots: IntIterable = slotRange): ItemStack { - if (this is IMatteryContainer) { - return this.addItem(stack, simulate, slots) - } - - if (stack.isEmpty) - return stack - - val copy = stack.copy() - - // двигаем в одинаковые слоты - var i = slots.intIterator() - - while (i.hasNext()) { - val slot = i.nextInt() - - if (ItemStack.isSameItemSameComponents(this[slot], copy)) { - val slotStack = this[slot] - val slotLimit = maxStackSize.coerceAtMost(slotStack.maxStackSize) - - if (slotStack.count < slotLimit) { - val newCount = (slotStack.count + copy.count).coerceAtMost(slotLimit) - val diff = newCount - slotStack.count - - if (!simulate) { - slotStack.count = newCount - setChanged() - } - - copy.shrink(diff) - - if (copy.isEmpty) { - return copy - } - } - } - } - - // двигаем в пустые слоты - i = slots.intIterator() - - while (i.hasNext()) { - val slot = i.nextInt() - - if (this[slot].isEmpty) { - val diff = copy.count.coerceAtMost(maxStackSize.coerceAtMost(copy.maxStackSize)) - - if (!simulate) { - val copyToPut = copy.copy() - copyToPut.count = diff - this[slot] = copyToPut - setChanged() - } - - copy.shrink(diff) - - if (copy.isEmpty) { - return copy - } - } - } - - return copy +fun Container.addItem(stack: ItemStack, simulate: Boolean, slots: IntSortedSet = slotRange): ItemStack { + return IEnhancedContainer.wrap(this).addItem(stack, simulate, slots) } fun Container.vanishCursedItems() { @@ -312,12 +237,18 @@ fun Container.sortWithIndices(sortedSlots: IntCollection) { if (value in 0 until containerSize && seen.add(value)) { val slot = containerSlot(value) - if ( - slot.isNotEmpty && - !slot.isForbiddenForAutomation && - slot.item.count <= slot.getMaxStackSize() && - (!slot.hasFilter || slot.getFilter() != slot.item.item || slot.getMaxStackSize() > 1) - ) { + val condition: Boolean + + if (slot is IFilteredContainerSlot) { + condition = slot.isNotEmpty && + !slot.isForbiddenForAutomation && + slot.item.count <= slot.maxStackSize(slot.item) && + (!slot.hasFilter || slot.filter != slot.item.item || slot.maxStackSize(slot.item) > 1) + } else { + condition = slot.isNotEmpty && slot.item.count <= slot.maxStackSize(slot.item) + } + + if (condition) { valid.add(slot) } } @@ -335,7 +266,7 @@ fun Container.computeSortedIndices(comparator: Comparator = ItemStack if (isEmpty) return IntList.of() - val slots = slotIterator().filter { !it.isForbiddenForAutomation && it.getMaxStackSize() >= it.item.count }.toList() + val slots = slotIterator().filter { (it !is IFilteredContainerSlot || !it.isForbiddenForAutomation) && it.maxStackSize(it.item) >= it.item.count }.toList() if (slots.isEmpty()) return IntList.of() diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerSlot.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerSlot.kt new file mode 100644 index 000000000..027939b62 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerSlot.kt @@ -0,0 +1,171 @@ +package ru.dbotthepony.mc.otm.container + +import net.minecraft.core.HolderLookup +import net.minecraft.core.registries.BuiltInRegistries +import net.minecraft.nbt.CompoundTag +import net.minecraft.nbt.NbtOps +import net.minecraft.resources.ResourceLocation +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.mc.otm.core.isNotEmpty +import ru.dbotthepony.mc.otm.core.nbt.set +import ru.dbotthepony.mc.otm.core.registryName +import ru.dbotthepony.mc.otm.data.getOrNull + +open class ContainerSlot( + final override val container: SlottedContainer, + final override val slot: Int +) : ISlottedContainerSlot, INBTSerializable { + private var _item: ItemStack = ItemStack.EMPTY + + final override var item: ItemStack + get() = _item + set(value) { + _item = value + setChanged() + } + + private var observedItem = ItemStack.EMPTY + + // called from inside setChanged + protected open fun notifyChanged(old: ItemStack) {} + + final override fun setChanged() { + observeChanges() + } + + /** + * Called when slot needs to be cleared of any data present + */ + open fun clear() { + _item = ItemStack.EMPTY + + if (observedItem.isNotEmpty) { + notifyChanged(observedItem) + } + + observedItem = ItemStack.EMPTY + } + + fun observeChanges(): Boolean { + if (observedItem.count != item.count || !ItemStack.isSameItemSameComponents(item, observedItem)) { + notifyChanged(observedItem) + observedItem = item.copy() + container.notifyChanged() + return true + } + + return false + } + + override fun remove(): ItemStack { + val item = item + + if (item.isEmpty) { + return ItemStack.EMPTY + } else { + this.item = ItemStack.EMPTY + return item + } + } + + override val maxStackSize: Int + get() = Item.DEFAULT_MAX_STACK_SIZE + + override fun remove(count: Int): ItemStack { + val item = item + + if (item.isEmpty) { + return ItemStack.EMPTY + } + + if (item.count >= count) { + this.item = ItemStack.EMPTY + return item + } else { + val split = item.split(count) + setChanged() + return split + } + } + + override fun serializeNBT(provider: HolderLookup.Provider): CompoundTag { + return CompoundTag().also { + it["item"] = ItemStack.OPTIONAL_CODEC.encodeStart(provider.createSerializationContext(NbtOps.INSTANCE), item) + .getOrThrow { RuntimeException("Unable to serialize $item in slot $slot: $it") } + } + } + + override fun deserializeNBT(provider: HolderLookup.Provider, nbt: CompoundTag) { + _item = ItemStack.OPTIONAL_CODEC.decode(provider.createSerializationContext(NbtOps.INSTANCE), nbt["item"]) + .ifError { LOGGER.error("Unable to deserialize item at slot $slot: ${it.message()}") } + .getOrNull()?.first ?: ItemStack.EMPTY + + observedItem = item.copy() + notifyChanged(ItemStack.EMPTY) + } + + open class Simple( + protected val listener: (new: ItemStack, old: ItemStack) -> Unit, + protected val maxStackSize: Int = Item.DEFAULT_MAX_STACK_SIZE, + ) : SlottedContainerBuilder.SlotProvider { + protected open inner class Instance(container: SlottedContainer, slot: Int) : ContainerSlot(container, slot) { + override val maxStackSize: Int + get() = this@Simple.maxStackSize + + override fun notifyChanged(old: ItemStack) { + 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), IFilteredSlottedContainerSlot { + 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"))) + } + } + } + + override fun create(container: SlottedContainer, index: Int): ContainerSlot { + return Instance(container, index) + } + } + + companion object { + private val LOGGER = LogManager.getLogger() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/IContainerSlot.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/IContainerSlot.kt index ee1ccfd22..e8860b407 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/IContainerSlot.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/IContainerSlot.kt @@ -1,9 +1,7 @@ package ru.dbotthepony.mc.otm.container import net.minecraft.world.Container -import net.minecraft.world.item.Item import net.minecraft.world.item.ItemStack -import net.minecraft.world.item.Items import ru.dbotthepony.kommons.util.Delegate import ru.dbotthepony.mc.otm.core.isNotEmpty @@ -15,54 +13,67 @@ interface IContainerSlot : Delegate { val slot: Int val container: Container + fun setChanged() + var item: ItemStack + operator fun component1() = slot operator fun component2() = item - fun getMaxStackSize(item: ItemStack = this.item): Int { - return container.maxStackSize - } - - val isForbiddenForAutomation: Boolean get() { - return getFilter() === Items.AIR - } - - fun getFilter(): Item? + /** + * Max stack size regardless of item + * + * Prefer to use ItemStack version instead + */ + val maxStackSize: Int /** - * @return whenever the filter was set. Returns false only if container can't be filtered. + * Max amount of [item] that can be stored in this slot. + * + * This may be larger or smaller than value returned [ItemStack.getMaxStackSize], + * and as such returned value by this method should be ground truth */ - fun setFilter(filter: Item? = null): Boolean - - val hasFilter: Boolean - get() = getFilter() != null - - fun remove() { - container[slot] = ItemStack.EMPTY + fun maxStackSize(item: ItemStack): Int { + return maxStackSize.coerceAtMost(item.maxStackSize) } - fun remove(count: Int): ItemStack { - return container.removeItem(slot, count) - } + fun remove(): ItemStack + fun remove(count: Int): ItemStack override fun get(): ItemStack { - return container[slot] + return item } override fun accept(t: ItemStack) { - container[slot] = t + item = t } - fun setChanged() { - container.setChanged() - } - - var item: ItemStack - get() = container[slot] - set(value) { container[slot] = value } - val isEmpty: Boolean - get() = container[slot].isEmpty + get() = item.isEmpty val isNotEmpty: Boolean - get() = container[slot].isNotEmpty + get() = item.isNotEmpty + + class Simple(override val slot: Int, override val container: Container) : IContainerSlot { + override fun setChanged() { + container.setChanged() + } + + override var item: ItemStack + get() = container[slot] + set(value) { container[slot] = value } + override val maxStackSize: Int + get() = container.maxStackSize + + override fun remove(count: Int): ItemStack { + return container.removeItem(slot, count) + } + + override fun remove(): ItemStack { + return container.removeItemNoUpdate(slot) + } + + override fun maxStackSize(item: ItemStack): Int { + return maxStackSize.coerceAtMost(item.maxStackSize) + } + } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/IEnhancedContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/IEnhancedContainer.kt new file mode 100644 index 000000000..f61bc6b86 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/IEnhancedContainer.kt @@ -0,0 +1,338 @@ +package ru.dbotthepony.mc.otm.container + +import it.unimi.dsi.fastutil.ints.IntSet +import net.minecraft.world.Container +import net.minecraft.world.entity.player.Player +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.Items +import net.minecraft.world.item.crafting.RecipeInput +import ru.dbotthepony.mc.otm.core.collect.any +import ru.dbotthepony.mc.otm.core.collect.filter +import ru.dbotthepony.mc.otm.core.collect.map +import ru.dbotthepony.mc.otm.core.isNotEmpty +import java.util.function.Predicate + +/** + * "Backward-compatible" enhanced container interface, where all methods can be derived/emulated from existing [IContainer] ([Container]) code + * + * This is useful because it allows to interact with actually enhanced and regular containers through unified interface, + * and actual implementations of this interface are likely to provide efficient method implementations in place of derived/emulated ones. + */ +interface IEnhancedContainer : IContainer, RecipeInput, Iterable { + fun containerSlot(slot: Int): IContainerSlot { + return IContainerSlot.Simple(slot, this) + } + + fun slotIterator(): Iterator { + return (0 until containerSize).iterator().map { containerSlot(it) } + } + + fun nonEmptySlotIterator(): Iterator { + return (0 until containerSize).iterator().map { containerSlot(it) }.filter { it.isNotEmpty } + } + + private fun slotIterator(allowedSlots: IntSet, predicate: Predicate): IntIterator { + return object : IntIterator() { + private val parent = allowedSlots.intIterator() + private var foundNext = false + private var next = -1 + + private fun findNext() { + if (!foundNext) { + foundNext = true + next = -1 + + while (parent.hasNext()) { + val i = parent.nextInt() + + if (predicate.test(this@IEnhancedContainer[i])) { + next = i + break + } + } + } + } + + override fun nextInt(): Int { + findNext() + + if (next == -1) + throw NoSuchElementException() + + foundNext = false + val next = next + this.next = -1 + return next + } + + override fun hasNext(): Boolean { + findNext() + return next != -1 + } + } + } + + fun emptySlotIterator(allowedSlots: IntSet = slotRange): IntIterator { + return slotIterator(allowedSlots) { it.isEmpty } + } + + fun nonEmptySlotIterator(allowedSlots: IntSet = slotRange): IntIterator { + return slotIterator(allowedSlots) { it.isNotEmpty } + } + + fun slotWithItemIterator(item: Item, allowedSlots: IntSet = slotRange): IntIterator { + return slotIterator(allowedSlots) { it.isNotEmpty && it.item === item } + } + + fun nextEmptySlot(startIndex: Int): Int { + for (i in startIndex until containerSize) { + if (this[i].isEmpty) { + return i + } + } + + return -1 + } + + fun nextNonEmptySlot(startIndex: Int): Int { + for (i in startIndex until containerSize) { + if (this[i].isNotEmpty) { + return i + } + } + + return -1 + } + + fun nextSlotWithItem(startIndex: Int, item: Item): Int { + var find = nextNonEmptySlot(startIndex) + + while (find != -1 && this[find].item !== item) + find = nextNonEmptySlot(find + 1) + + return find + } + + fun setChanged(slot: Int) { + return setChanged() + } + + fun getMaxStackSize(slot: Int, itemStack: ItemStack): Int { + return maxStackSize + } + + override fun iterator(): Iterator { + return (0 until containerSize).iterator().map { this[it] }.filter { it.isNotEmpty } + } + + override fun isEmpty(): Boolean { + return super.isEmpty() + } + + val hasEmptySlots: Boolean get() { + for (i in 0 until containerSize) { + if (this[i].isEmpty) { + return true + } + } + + return false + } + + override fun size(): Int { + return containerSize + } + + override fun countItem(item: Item): Int { + var count = 0 + + for (stack in this) { + if (stack.item === item) { + count += stack.count + } + } + + return count + } + + override fun hasAnyOf(items: Set): Boolean { + if (Items.AIR in items && hasEmptySlots) + return true + + return iterator().any { it.item in items } + } + + override fun hasAnyMatching(predicate: Predicate): Boolean { + if (predicate.test(ItemStack.EMPTY) && hasEmptySlots) + return true + + return iterator().any(predicate) + } + + fun toList(): MutableList { + val list = ArrayList(containerSize) + + for (i in 0 until containerSize) { + list.add(this[i]) + } + + return list + } + + fun addItem(stack: ItemStack, simulate: Boolean, slots: IntSet = slotRange, onlyIntoExisting: Boolean = false, popTime: Int? = null): ItemStack { + if (stack.isEmpty || slots.isEmpty()) + return stack + + val copy = stack.copy() + + // двигаем в одинаковые слоты + for (slot in slotWithItemIterator(stack.item, slots)) { + if (ItemStack.isSameItemSameComponents(this[slot], copy)) { + val slotStack = this[slot] + val slotLimit = getMaxStackSize(slot, slotStack) + + if (slotStack.count < slotLimit) { + val newCount = (slotStack.count + copy.count).coerceAtMost(slotLimit) + val diff = newCount - slotStack.count + + if (!simulate) { + slotStack.count = newCount + setChanged(slot) + + if (popTime != null) { + slotStack.popTime = popTime + } + } + + copy.shrink(diff) + + if (copy.isEmpty) { + return ItemStack.EMPTY + } + } + } + } + + if (!onlyIntoExisting) { + // двигаем в пустые слоты + for (slot in emptySlotIterator(slots)) { + val diff = copy.count.coerceAtMost(getMaxStackSize(slot, stack)) + + if (!simulate) { + val copyToPut = copy.copy() + copyToPut.count = diff + this[slot] = copyToPut + setChanged() + } + + copy.shrink(diff) + + if (copy.isEmpty) + return ItemStack.EMPTY + } + } + + return copy + } + + /** + * Unlike [addItem], modifies original [stack] + * + * @return Whenever [stack] was modified + */ + fun consumeItem(stack: ItemStack, simulate: Boolean, slots: IntSet = slotRange, onlyIntoExisting: Boolean = false, popTime: Int? = null): Boolean { + if (stack.isEmpty || slots.isEmpty()) + return false + + val result = addItem(stack, simulate, slots, onlyIntoExisting, popTime) + if (!simulate) stack.count = result.count + return result.count != stack.count + } + + fun fullyAddItem(stack: ItemStack, slots: IntSet = slotRange, onlyIntoExisting: Boolean = false, popTime: Int? = null): Boolean { + if (!addItem(stack, true, slots, onlyIntoExisting, popTime).isEmpty) + return false + + return addItem(stack, false, slots, onlyIntoExisting, popTime).isEmpty + } + + private class Wrapper(private val parent: Container) : IEnhancedContainer { + override fun clearContent() { + return parent.clearContent() + } + + override fun setChanged() { + return parent.setChanged() + } + + override fun getContainerSize(): Int { + return parent.containerSize + } + + override fun getItem(slot: Int): ItemStack { + return parent.getItem(slot) + } + + override fun removeItem(slot: Int, amount: Int): ItemStack { + return parent.removeItem(slot, amount) + } + + override fun removeItemNoUpdate(slot: Int): ItemStack { + return parent.removeItemNoUpdate(slot) + } + + override fun setItem(slot: Int, itemStack: ItemStack) { + return parent.setItem(slot, itemStack) + } + + override fun stillValid(player: Player): Boolean { + return parent.stillValid(player) + } + + override fun getMaxStackSize(): Int { + return parent.maxStackSize + } + + override fun startOpen(player: Player) { + parent.startOpen(player) + } + + override fun stopOpen(player: Player) { + parent.stopOpen(player) + } + + override fun canPlaceItem(slot: Int, itemStack: ItemStack): Boolean { + return parent.canPlaceItem(slot, itemStack) + } + + override fun canTakeItem(container: Container, slot: Int, itemStack: ItemStack): Boolean { + return parent.canTakeItem(container, slot, itemStack) + } + + override fun isEmpty(): Boolean { + return parent.isEmpty + } + + override fun hasAnyMatching(predicate: Predicate): Boolean { + return parent.hasAnyMatching(predicate) + } + + override fun hasAnyOf(items: Set): Boolean { + return parent.hasAnyOf(items) + } + + override fun countItem(item: Item): Int { + return parent.countItem(item) + } + } + + companion object { + fun wrap(other: Container): IEnhancedContainer { + if (other is IEnhancedContainer) + return other + + return Wrapper(other) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/IFilteredContainerSlot.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/IFilteredContainerSlot.kt new file mode 100644 index 000000000..054f0719d --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/IFilteredContainerSlot.kt @@ -0,0 +1,29 @@ +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 { + var filter: Item? + + 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 + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/IFilteredSlottedContainerSlot.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/IFilteredSlottedContainerSlot.kt new file mode 100644 index 000000000..91f1d9da2 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/IFilteredSlottedContainerSlot.kt @@ -0,0 +1,9 @@ +package ru.dbotthepony.mc.otm.container + +import net.minecraft.world.item.ItemStack + +interface IFilteredSlottedContainerSlot : IFilteredContainerSlot, ISlottedContainerSlot { + override fun canAutomationPlaceItem(itemStack: ItemStack): Boolean { + return super.canAutomationPlaceItem(itemStack) && testSlotFilter(itemStack) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/IMatteryContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/IMatteryContainer.kt index 03644d292..6dc2e7e87 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/IMatteryContainer.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/IMatteryContainer.kt @@ -50,27 +50,6 @@ interface IMatteryContainer : IContainer, RecipeInput, Iterable { } } - open class ContainerSlot(override val slot: Int, override val container: IMatteryContainer) : IContainerSlot { - override val isForbiddenForAutomation: Boolean - get() = container.isSlotForbiddenForAutomation(slot) - - override fun getFilter(): Item? { - return container.getSlotFilter(slot) - } - - override fun setFilter(filter: Item?): Boolean { - return container.setSlotFilter(slot, filter) - } - - override fun getMaxStackSize(item: ItemStack): Int { - return container.getMaxStackSize(slot, item) - } - - override fun setChanged() { - container.setChanged(slot) - } - } - /** * Iterates either non-empty slots of container or all slots of container */ @@ -83,7 +62,7 @@ interface IMatteryContainer : IContainer, RecipeInput, Iterable { } fun containerSlot(slot: Int): IContainerSlot { - return ContainerSlot(slot, this) + return IContainerSlot.Simple(slot, this) } fun hasSlotFilter(slot: Int) = getSlotFilter(slot) !== null @@ -234,22 +213,4 @@ interface IMatteryContainer : IContainer, RecipeInput, Iterable { return list } - - fun shrink(slot: Int, amount: Int): Boolean { - if (slot < 0 || slot > size()) - return false - - val item = this[slot] - if (item.isEmpty) - return false - - if (item.count <= amount) { - this[slot] = ItemStack.EMPTY - } else { - item.shrink(amount) - setChanged(slot) - } - - return true - } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ISlottedContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ISlottedContainer.kt new file mode 100644 index 000000000..dfccd28e0 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ISlottedContainer.kt @@ -0,0 +1,220 @@ +package ru.dbotthepony.mc.otm.container + +import it.unimi.dsi.fastutil.ints.IntSet +import net.minecraft.world.Container +import net.minecraft.world.item.ItemStack +import net.neoforged.neoforge.items.IItemHandler +import ru.dbotthepony.kommons.collect.any +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, + * and subsequently fully implement [IItemHandler] + */ +interface ISlottedContainer : IEnhancedContainer, IItemHandler { + override fun containerSlot(slot: Int): ISlottedContainerSlot + + 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() + } + + override fun getMaxStackSize(slot: Int, itemStack: ItemStack): Int { + return containerSlot(slot).maxStackSize(itemStack) + } + + override fun getItem(slot: Int): ItemStack { + return containerSlot(slot).item + } + + override fun removeItem(slot: Int, amount: Int): ItemStack { + return containerSlot(slot).remove(amount) + } + + override fun removeItemNoUpdate(slot: Int): ItemStack { + return containerSlot(slot).remove() + } + + override fun setItem(slot: Int, itemStack: ItemStack) { + containerSlot(slot).item = itemStack + } + + override fun canPlaceItem(slot: Int, itemStack: ItemStack): Boolean { + return containerSlot(slot).canAutomationPlaceItem(itemStack) + } + + override fun canTakeItem(container: Container, slot: Int, itemStack: ItemStack): Boolean { + return containerSlot(slot).canAutomationTakeItem() + } + + override fun getSlots() = containerSize + override fun getStackInSlot(slot: Int) = containerSlot(slot).item + + override fun insertItem(slot: Int, stack: ItemStack, simulate: Boolean): ItemStack { + return containerSlot(slot).insertItem(stack, simulate) + } + + override fun extractItem(slot: Int, amount: Int, simulate: Boolean): ItemStack { + return containerSlot(slot).extractItem(amount, simulate) + } + + override fun getSlotLimit(slot: Int): Int { + return containerSlot(slot).maxStackSize + } + + override fun isItemValid(slot: Int, stack: ItemStack): Boolean { + return canPlaceItem(slot, stack) + } + + private fun addItem(stack: ItemStack, simulate: Boolean, filterPass: Boolean, slots: IntSet, onlyIntoExisting: Boolean, popTime: Int?, ignoreFilters: Boolean): ItemStack { + if (stack.isEmpty || slots.isEmpty()) + return stack + + // двигаем в одинаковые слоты + for (i in slotWithItemIterator(stack.item, slots)) { + val slot = containerSlot(i) + + val condition: Boolean + + if (slot is IFilteredContainerSlot) { + condition = (ignoreFilters || !slot.isForbiddenForAutomation) && + ItemStack.isSameItemSameComponents(slot.item, stack) && + (ignoreFilters || !filterPass && !slot.hasFilter || filterPass && slot.hasFilter && slot.testSlotFilter(stack)) + } else { + condition = (ignoreFilters || !filterPass) && ItemStack.isSameItemSameComponents(slot.item, stack) + } + + if (condition) { + val slotLimit = slot.maxStackSize(slot.item) + + if (slot.item.count < slotLimit) { + val newCount = (slot.item.count + stack.count).coerceAtMost(slotLimit) + val diff = newCount - slot.item.count + + if (!simulate) { + slot.item.count = newCount + slot.setChanged() + + if (popTime != null) { + slot.item.popTime = popTime + } + } + + stack.shrink(diff) + + if (stack.isEmpty) + return ItemStack.EMPTY + } + } + } + + if (!onlyIntoExisting) { + for (i in emptySlotIterator(slots)) { + val slot = containerSlot(i) + + val condition: Boolean + + if (slot is IFilteredContainerSlot) { + condition = (ignoreFilters || !slot.isForbiddenForAutomation) && + (ignoreFilters || !filterPass && !slot.hasFilter || filterPass && slot.hasFilter && slot.testSlotFilter(stack)) + } else { + condition = ignoreFilters || !filterPass + } + + if (condition) { + val diff = stack.count.coerceAtMost(slot.maxStackSize(stack)) + + if (!simulate) { + val copyToPut = stack.copy() + copyToPut.count = diff + slot.item = copyToPut + + if (popTime != null) { + copyToPut.popTime = popTime + } + } + + stack.shrink(diff) + + if (stack.isEmpty) + return ItemStack.EMPTY + } + } + } + + return stack + } + + /** + * Hint used internally by [ISlottedContainer] to potentially speed up default method implementations + */ + 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 { + if (stack.isEmpty || slots.isEmpty()) + return stack + + if (ignoreFilters || !hasFilterableSlots) { + return addItem(stack.copy(), simulate, filterPass = true, slots, onlyIntoExisting = onlyIntoExisting, popTime = popTime, ignoreFilters = true) + } else { + var copy = addItem(stack.copy(), simulate, filterPass = true, slots, onlyIntoExisting = onlyIntoExisting, popTime = popTime, ignoreFilters = false) + copy = addItem(copy, simulate, filterPass = false, slots, onlyIntoExisting = onlyIntoExisting, popTime = popTime, ignoreFilters = false) + return copy + } + } + + + /** + * Unlike [addItem], modifies original [stack] + * + * @return Whenever [stack] was modified + */ + fun consumeItem(stack: ItemStack, simulate: Boolean, slots: IntSet = slotRange, onlyIntoExisting: Boolean = false, popTime: Int? = null, ignoreFilters: Boolean): Boolean { + if (stack.isEmpty) + return false + + val result = addItem(stack, simulate, slots, onlyIntoExisting = onlyIntoExisting, popTime = popTime, ignoreFilters = ignoreFilters) + if (!simulate) stack.count = result.count + return result.count != stack.count + } + + fun fullyAddItem(stack: ItemStack, slots: IntSet = slotRange, onlyIntoExisting: Boolean = false, popTime: Int? = null, ignoreFilters: Boolean): Boolean { + if (!addItem(stack, true, slots, popTime = popTime, onlyIntoExisting = onlyIntoExisting, ignoreFilters = ignoreFilters).isEmpty) + return false + + return addItem(stack, false, slots, popTime = popTime, onlyIntoExisting = onlyIntoExisting, ignoreFilters = ignoreFilters).isEmpty + } + + override fun addItem( + stack: ItemStack, + simulate: Boolean, + slots: IntSet, + onlyIntoExisting: Boolean, + popTime: Int? + ): ItemStack { + return addItem(stack, simulate, slots, onlyIntoExisting, popTime, ignoreFilters = false) + } + + override fun consumeItem( + stack: ItemStack, + simulate: Boolean, + slots: IntSet, + onlyIntoExisting: Boolean, + popTime: Int? + ): Boolean { + return consumeItem(stack, simulate, slots, onlyIntoExisting, popTime, ignoreFilters = false) + } + + override fun fullyAddItem(stack: ItemStack, slots: IntSet, onlyIntoExisting: Boolean, popTime: Int?): Boolean { + return fullyAddItem(stack, slots, onlyIntoExisting, popTime, ignoreFilters = false) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ISlottedContainerSlot.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ISlottedContainerSlot.kt new file mode 100644 index 000000000..23db5590a --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ISlottedContainerSlot.kt @@ -0,0 +1,95 @@ +package ru.dbotthepony.mc.otm.container + +import net.minecraft.world.item.ItemStack +import net.neoforged.neoforge.items.IItemHandler + +/** + * Slot of [ISlottedContainer], with additional methods to implement interaction behavior for both for players and mechanisms + */ +interface ISlottedContainerSlot : IContainerSlot { + override val container: ISlottedContainer + + fun canAutomationPlaceItem(itemStack: ItemStack): Boolean { + return true + } + + fun canAutomationTakeItem(desired: Int = item.count): Boolean { + return true + } + + fun modifyAutomationPlaceCount(itemStack: ItemStack): Int { + return itemStack.count + } + + fun modifyAutomationExtractionCount(desired: Int): Int { + return desired + } + + /** + * Slot-specific implementation for [IItemHandler.insertItem] + */ + fun insertItem(stack: ItemStack, simulate: Boolean): ItemStack { + if (!canAutomationPlaceItem(stack)) + return stack + + var amount = modifyAutomationPlaceCount(stack) + + if (amount <= 0) + return stack + + if (item.isEmpty) { + amount = stack.count.coerceAtMost(maxStackSize(stack)).coerceAtMost(amount) + + if (!simulate) { + item = stack.copyWithCount(amount) + } + + if (stack.count <= amount) { + return ItemStack.EMPTY + } else { + return stack.copyWithCount(stack.count - amount) + } + } else if (item.isStackable && maxStackSize(item) > item.count && ItemStack.isSameItemSameComponents(item, stack)) { + val newCount = maxStackSize(item).coerceAtMost(item.count + stack.count.coerceAtMost(amount)) + val diff = newCount - item.count + + if (diff != 0) { + if (!simulate) { + item.grow(diff) + setChanged() + } + + val copy = stack.copy() + copy.shrink(diff) + return copy + } + } + + return stack + } + + fun extractItem(amount: Int, simulate: Boolean): ItemStack { + if (amount <= 0 || !canAutomationTakeItem(amount) || item.isEmpty) + return ItemStack.EMPTY + + @Suppress("name_shadowing") + val amount = modifyAutomationExtractionCount(amount) + if (amount <= 0 || !canAutomationTakeItem(amount)) return ItemStack.EMPTY + + val minimal = amount.coerceAtMost(item.count) + val copy = item.copy() + copy.count = minimal + + if (!simulate) { + if (item.count == minimal) { + item = ItemStack.EMPTY + } else { + item.shrink(minimal) + } + + setChanged() + } + + return copy + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryContainer.kt index 7cee2b9e7..53b155f8d 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryContainer.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryContainer.kt @@ -426,22 +426,32 @@ open class MatteryContainer(var listener: ContainerListener, private val size: I } } - private inner class Slot(override val slot: Int) : IContainerSlot { + private inner class Slot(override val slot: Int) : IFilteredContainerSlot { override val container: Container get() = this@MatteryContainer + override var item: ItemStack + get() = this@MatteryContainer[slot] + set(value) { this@MatteryContainer[slot] = value } + override val maxStackSize: Int + get() = this@MatteryContainer.maxStackSize + + override fun remove(): ItemStack { + return removeItemNoUpdate(slot) + } + + override fun remove(count: Int): ItemStack { + return removeItem(slot, count) + } + + override var filter: Item? + get() = getSlotFilter(slot) + set(value) { setSlotFilter(slot, value) } + override val isForbiddenForAutomation: Boolean get() = isSlotForbiddenForAutomation(slot) - override fun getFilter(): Item? { - return getSlotFilter(slot) - } - - override fun setFilter(filter: Item?): Boolean { - return setSlotFilter(slot, filter) - } - - override fun getMaxStackSize(item: ItemStack): Int { + override fun maxStackSize(item: ItemStack): Int { return getMaxStackSize(slot, item) } @@ -450,12 +460,12 @@ open class MatteryContainer(var listener: ContainerListener, private val size: I } } - final override fun slotIterator(): kotlin.collections.Iterator { + final override fun slotIterator(): kotlin.collections.Iterator { indicesReferenced = true return nonEmptyIndices.iterator().map { Slot(it) } } - final override fun slotIterator(nonEmpty: Boolean): kotlin.collections.Iterator { + final override fun slotIterator(nonEmpty: Boolean): kotlin.collections.Iterator { if (!nonEmpty) { return (0 until size).iterator().map { Slot(it) } } else if (isEmpty) { @@ -466,7 +476,7 @@ open class MatteryContainer(var listener: ContainerListener, private val size: I } } - final override fun containerSlot(slot: Int): IContainerSlot { + final override fun containerSlot(slot: Int): IFilteredContainerSlot { return Slot(slot) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/SlottedContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/SlottedContainer.kt new file mode 100644 index 000000000..6d81f58e1 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/SlottedContainer.kt @@ -0,0 +1,177 @@ +package ru.dbotthepony.mc.otm.container + +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder +import net.minecraft.core.HolderLookup +import net.minecraft.core.registries.BuiltInRegistries +import net.minecraft.nbt.CompoundTag +import net.minecraft.nbt.ListTag +import net.minecraft.nbt.NbtOps +import net.minecraft.nbt.Tag +import net.minecraft.world.entity.player.Player +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.mc.otm.data.codec.minRange +import java.util.function.Predicate + +class SlottedContainer( + slots: Collection, + private val stillValid: Predicate, + private val globalChangeListeners: Array +) : ISlottedContainer, INBTSerializable { + private val slots: Array + + init { + val itr = slots.iterator() + this.slots = Array(slots.size) { itr.next().create(this, it) } + } + + override val hasFilterableSlots: Boolean = this.slots.any { it is IFilteredContainerSlot } + private var suppressListeners = false + + override fun clearContent() { + suppressListeners = true + + try { + slots.forEach { it.remove() } + notifyChanged() + } finally { + suppressListeners = false + } + } + + override fun containerSlot(slot: Int): ISlottedContainerSlot { + return slots[slot] + } + + fun notifyChanged() { + if (suppressListeners) return + globalChangeListeners.forEach { it.run() } + } + + // called by outside code (vanilla and other unaware mods) + override fun setChanged() { + suppressListeners = true + var hasChanges = false + + try { + slots.forEach { hasChanges = it.observeChanges() || hasChanges } + } finally { + suppressListeners = false + + if (hasChanges) + notifyChanged() + } + } + + override fun getContainerSize(): Int { + return slots.size + } + + override fun stillValid(player: Player): Boolean { + return stillValid.test(player) + } + + private data class LegacySerializedItem(val item: ItemStack, val slot: Int) { + companion object { + val CODEC: Codec = RecordCodecBuilder.create { + it.group( + ItemStack.OPTIONAL_CODEC.fieldOf("item").forGetter { it.item }, + Codec.INT.minRange(0).fieldOf("slot").forGetter { it.slot }, + ).apply(it, ::LegacySerializedItem) + } + } + } + + private data class LegacySerializedFilter(val item: Item, val slot: Int) { + companion object { + val CODEC: Codec = RecordCodecBuilder.create { + it.group( + BuiltInRegistries.ITEM.byNameCodec().fieldOf("item").forGetter { it.item }, + Codec.INT.minRange(0).fieldOf("slot").forGetter { it.slot }, + ).apply(it, ::LegacySerializedFilter) + } + } + } + + private data class LegacySerializedState( + val items: List, + val filters: List + ) { + companion object { + val CODEC: Codec = RecordCodecBuilder.create { + it.group( + Codec.list(LegacySerializedItem.CODEC).fieldOf("items").forGetter { it.items }, + Codec.list(LegacySerializedFilter.CODEC).fieldOf("filters").forGetter { it.filters }, + ).apply(it, ::LegacySerializedState) + } + } + } + + private val lostItems = ArrayList() + + override fun serializeNBT(provider: HolderLookup.Provider): ListTag { + return ListTag().also { + for (slot in slots) { + it.add(slot.serializeNBT(provider)) + } + + it.addAll(lostItems) + } + } + + override fun deserializeNBT(provider: HolderLookup.Provider, nbt: Tag) { + lostItems.clear() + slots.forEach { it.clear() } + + if (nbt is CompoundTag) { + // legacy container + LegacySerializedState.CODEC.decode(provider.createSerializationContext(NbtOps.INSTANCE), nbt) + .resultOrPartial { LOGGER.error("Error deserializing container: $it") } + .ifPresent { + val (items, filters) = it.first + + // excessive items will be lost + for ((item, slot) in items) { + if (slot in 0 until containerSize) { + slots[slot].item = item + } + } + + for ((filter, slot) in filters) { + if (slot in 0 until containerSize) { + val getSlot = slots[slot] + + if (getSlot is IFilteredContainerSlot) { + getSlot.filter = filter + } + } + } + } + } else if (nbt is ListTag) { + // normal container + for ((i, element) in nbt.withIndex()) { + if (element !is CompoundTag) { + LOGGER.error("Deserializing mattery container: Expected compound tag at $i, got $element") + continue + } + + if (i in 0 until containerSize) { + slots[i].deserializeNBT(provider, element) + } else { + lostItems.add(element) + } + } + } else { + LOGGER.error("Unable to deserialize mattery container, expected CompoundTag or ListTag, got $nbt") + } + + notifyChanged() + } + + companion object { + private val LOGGER = LogManager.getLogger() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/SlottedContainerBuilder.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/SlottedContainerBuilder.kt new file mode 100644 index 000000000..5edb2d5b0 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/SlottedContainerBuilder.kt @@ -0,0 +1,48 @@ +package ru.dbotthepony.mc.otm.container + +import net.minecraft.world.entity.player.Player +import java.util.function.Predicate + +class SlottedContainerBuilder { + fun interface SlotProvider { + fun create(container: SlottedContainer, index: Int): ContainerSlot + } + + private val slots = ArrayList() + private var stillValid = Predicate { true } + private val globalChangeListeners = ArrayList() + + fun add(slot: SlotProvider): SlottedContainerBuilder { + slots.add(slot) + return this + } + + fun add(amount: Int, provider: SlotProvider): SlottedContainerBuilder { + for (i in 0 until amount) + slots.add(provider) + + return this + } + + fun stillValid(predicate: Predicate): SlottedContainerBuilder { + this.stillValid = predicate + return this + } + + fun onChanged(listener: Runnable): SlottedContainerBuilder { + globalChangeListeners.add(listener) + return this + } + + fun copy(): SlottedContainerBuilder { + val copy = SlottedContainerBuilder() + copy.slots.addAll(slots) + copy.globalChangeListeners.addAll(globalChangeListeners) + copy.stillValid = stillValid + return copy + } + + fun build(): SlottedContainer { + return SlottedContainer(slots, stillValid, globalChangeListeners.toTypedArray()) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/util/Iterators.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/util/Iterators.kt index 1dd6bad9c..64f4a9b73 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/util/Iterators.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/util/Iterators.kt @@ -4,52 +4,41 @@ import net.minecraft.world.Container import net.minecraft.world.item.Item import net.minecraft.world.item.ItemStack import ru.dbotthepony.mc.otm.container.IContainerSlot +import ru.dbotthepony.mc.otm.container.IEnhancedContainer import ru.dbotthepony.mc.otm.container.IMatteryContainer import ru.dbotthepony.mc.otm.container.get import ru.dbotthepony.mc.otm.core.collect.filter import ru.dbotthepony.mc.otm.core.collect.map import ru.dbotthepony.mc.otm.core.isNotEmpty -class SimpleContainerSlot(override val slot: Int, override val container: Container) : IContainerSlot { - init { - require(slot in 0 until container.containerSize) { "Slot out of bounds: $slot (container: $container with size ${container.containerSize})" } - } - - override fun getFilter(): Item? { - return null - } - - override fun setFilter(filter: Item?): Boolean { - return false - } -} - fun Container.containerSlot(slot: Int): IContainerSlot { - if (this is IMatteryContainer) { + if (this is IEnhancedContainer) { return containerSlot(slot) } else { - return SimpleContainerSlot(slot, this) + return IContainerSlot.Simple(slot, this) } } -operator fun Container.iterator() = iterator(true) - -fun Container.iterator(nonEmpty: Boolean): Iterator { - if (this is IMatteryContainer) { - return iterator(nonEmpty) - } else if (nonEmpty) { +operator fun Container.iterator(): Iterator { + if (this is IEnhancedContainer) { + return iterator() + } else { return (0 until containerSize).iterator().map { this[it] }.filter { it.isNotEmpty } - } else { - return (0 until containerSize).iterator().map { this[it] } } } -fun Container.slotIterator(nonEmpty: Boolean = true): Iterator { - if (this is IMatteryContainer) { - return slotIterator(nonEmpty) - } else if (nonEmpty) { - return (0 until containerSize).iterator().filter { this[it].isNotEmpty }.map { SimpleContainerSlot(it, this) } +fun Container.slotIterator(): Iterator { + if (this is IEnhancedContainer) { + return slotIterator() } else { - return (0 until containerSize).iterator().map { SimpleContainerSlot(it, this) } + return (0 until containerSize).iterator().map { IContainerSlot.Simple(it, this) } + } +} + +fun Container.nonEmptySlotIterator(): Iterator { + if (this is IEnhancedContainer) { + return nonEmptySlotIterator() + } else { + return (0 until containerSize).iterator().filter { this[it].isNotEmpty }.map { IContainerSlot.Simple(it, this) } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/IntRange2Set.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/IntRange2Set.kt new file mode 100644 index 000000000..fccb08844 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/IntRange2Set.kt @@ -0,0 +1,206 @@ +package ru.dbotthepony.mc.otm.core.collect + +import it.unimi.dsi.fastutil.HashCommon +import it.unimi.dsi.fastutil.ints.IntBidirectionalIterator +import it.unimi.dsi.fastutil.ints.IntCollection +import it.unimi.dsi.fastutil.ints.IntComparator +import it.unimi.dsi.fastutil.ints.IntIterators +import it.unimi.dsi.fastutil.ints.IntSet +import it.unimi.dsi.fastutil.ints.IntSortedSet + +class IntRange2Set private constructor(private val first: Int, private val last: Int) : IntSortedSet { + constructor(range: IntRange) : this(range.first, range.last) { + require(range.step == 1) { "Provided range has non-standard step of ${range.step}" } + } + + @Deprecated("Not supported", level = DeprecationLevel.ERROR) + override fun add(element: Int): Boolean { + throw UnsupportedOperationException() + } + + @Deprecated("Not supported", level = DeprecationLevel.ERROR) + override fun addAll(c: IntCollection?): Boolean { + throw UnsupportedOperationException() + } + + @Deprecated("Not supported", level = DeprecationLevel.ERROR) + override fun addAll(elements: Collection): Boolean { + throw UnsupportedOperationException() + } + + @Deprecated("Not supported", level = DeprecationLevel.ERROR) + override fun clear() { + throw UnsupportedOperationException() + } + + override fun iterator(fromElement: Int): IntBidirectionalIterator { + if (isEmpty() || fromElement > last) + return IntIterators.EMPTY_ITERATOR + else if (fromElement <= first) + return IntIterators.fromTo(first, last + 1) + else + return IntIterators.fromTo(fromElement, last + 1) + } + + override fun iterator(): IntBidirectionalIterator { + if (isEmpty()) + return IntIterators.EMPTY_ITERATOR + + return IntIterators.fromTo(first, last + 1) + } + + @Deprecated("Not supported", level = DeprecationLevel.ERROR) + override fun remove(k: Int): Boolean { + throw UnsupportedOperationException() + } + + @Deprecated("Not supported", level = DeprecationLevel.ERROR) + override fun removeAll(c: IntCollection?): Boolean { + throw UnsupportedOperationException() + } + + @Deprecated("Not supported", level = DeprecationLevel.ERROR) + override fun removeAll(elements: Collection): Boolean { + throw UnsupportedOperationException() + } + + @Deprecated("Not supported", level = DeprecationLevel.ERROR) + override fun retainAll(c: IntCollection?): Boolean { + throw UnsupportedOperationException() + } + + @Deprecated("Not supported", level = DeprecationLevel.ERROR) + override fun retainAll(elements: Collection): Boolean { + throw UnsupportedOperationException() + } + + override fun contains(key: Int): Boolean { + return key in first .. last + } + + override fun containsAll(c: IntCollection): Boolean { + return c.ktIterator().all { it in first .. last } + } + + override fun containsAll(elements: Collection): Boolean { + return elements.all { it in first .. last } + } + + override fun isEmpty(): Boolean { + return last > first + } + + override fun toArray(a: IntArray?): IntArray { + if (a == null || a.size < size) + return toIntArray() + + var index = 0 + + for (i in first .. last) { + a[index++] = i + } + + return a + } + + override fun toIntArray(): IntArray { + if (isEmpty()) + return EMPTY_ARRAY + + return IntArray(last - first + 1) { first + it } + } + + override fun comparator(): IntComparator? { + return null + } + + override fun subSet(fromElement: Int, toElement: Int): IntSortedSet { + if (fromElement <= first && toElement > last) + return this + else if (fromElement <= first) + return IntRange2Set(first, toElement - 1) + else if (toElement > last) + return IntRange2Set(fromElement, last) + else + return EMPTY + } + + override fun headSet(toElement: Int): IntSortedSet { + if (isEmpty() || toElement <= first) + return EMPTY + else if (toElement < last) + return this + else + return IntRange2Set(first, toElement - 1) + } + + override fun tailSet(fromElement: Int): IntSortedSet { + if (isEmpty() || fromElement > last) + return EMPTY + else if (fromElement < first) + return this + else + return IntRange2Set(fromElement, last) + } + + override fun firstInt(): Int { + if (isEmpty()) + throw NoSuchElementException("Range is empty") + + return first + } + + override fun lastInt(): Int { + if (isEmpty()) + throw NoSuchElementException("Range is empty") + + return last + } + + override val size: Int + get() = if (last > first) 0 else last - first + 1 + + override fun toString(): String { + if (isEmpty()) + return "IntRange2Set[EMPTY]" + + return "IntRange2Set[$first .. $last]" + } + + override fun equals(other: Any?): Boolean { + return this === other || + other is IntRange2Set && (isEmpty() == other.isEmpty() || first == other.first && last == other.last) || + other is IntSet && other.size == size && containsAll(other) || + other is Set<*> && other.size == size && other.all { it is Int && it in first .. last } + } + + override fun hashCode(): Int { + if (isEmpty()) + return EMPTY_HASH + + return HashCommon.murmurHash3(last - first) + } + + companion object { + /** + * Returns set containing values beginning from [from] (inclusive) to [to] (inclusive) + */ + fun closed(from: Int, to: Int): IntRange2Set { + if (from > to) + return EMPTY + + return IntRange2Set(from, to) + } + + /** + * Returns set containing values beginning from [from] (inclusive) to [to] (exclusive) + */ + fun openEnded(from: Int, to: Int): IntRange2Set { + return closed(from, to - 1) + } + + private val EMPTY_ARRAY = IntArray(0) + val EMPTY = IntRange2Set(0, -1) + private val EMPTY_HASH = HashCommon.murmurHash3(0) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/Iterables.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/Iterables.kt index 469b95915..4c1a23635 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/Iterables.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/Iterables.kt @@ -1,7 +1,9 @@ package ru.dbotthepony.mc.otm.core.collect +import it.unimi.dsi.fastutil.ints.IntCollection import it.unimi.dsi.fastutil.ints.IntIterable import it.unimi.dsi.fastutil.ints.IntIterator +import it.unimi.dsi.fastutil.ints.IntSortedSet fun IntRange.asIterable(): IntIterable { return IntIterable { @@ -22,3 +24,31 @@ fun IntRange.asIterable(): IntIterable { } } } + +fun IntCollection.ktIterator(): kotlin.collections.IntIterator { + return object : kotlin.collections.IntIterator() { + private val parent = this@ktIterator.intIterator() + + override fun nextInt(): Int { + return parent.nextInt() + } + + override fun hasNext(): Boolean { + return parent.hasNext() + } + } +} + +fun IntSortedSet.ktIterator(fromElement: Int): kotlin.collections.IntIterator { + return object : kotlin.collections.IntIterator() { + private val parent = this@ktIterator.iterator(fromElement) + + override fun nextInt(): Int { + return parent.nextInt() + } + + override fun hasNext(): Boolean { + return parent.hasNext() + } + } +}